def generate_card_results(self, tracks: List[Song], cards: List[BingoTicket]): """generate PDF showing when each ticket wins""" doc = DG.Document(pagesize=PageSizes.A4, title=f'{self.options.game_id} - {self.options.title}', topMargin="0.25in", bottomMargin="0.25in", rightMargin="0.25in", leftMargin="0.25in") doc.append(self.options.palette.logo_image("6.2in")) doc.append(DG.Spacer(width=0, height="0.05in")) doc.append(DG.Paragraph( f'Results For Game Number: <b>{self.options.game_id}</b>', self.TEXT_STYLES['results-heading'])) doc.append(DG.Paragraph( self.options.title, self.TEXT_STYLES['results-title'])) pstyle = self.TEXT_STYLES['results-cell'] heading: DG.TableRow = [ DG.Paragraph('<b>Ticket Number</b>', pstyle), DG.Paragraph('<b>Wins after track</b>', pstyle), DG.Paragraph('<b>Start Time</b>', pstyle), ] data: List[DG.TableRow] = [] cards = copy.copy(cards) cards.sort(key=lambda card: card.ticket_number, reverse=False) for card in cards: win_point = self.get_when_ticket_wins(tracks, card) song = tracks[win_point - 1] data.append([ DG.Paragraph(f'{card.ticket_number}', pstyle), DG.Paragraph( f'Track {win_point} - {song.title} ({song.artist})', pstyle), DG.Paragraph(Duration(song.start_time).format(), pstyle) ]) col_widths: List[Dimension] = [ Dimension("0.75in"), Dimension("5.5in"), Dimension("0.85in"), ] hstyle = pstyle.replace( name='results-table-heading', background=self.options.palette.title_bg) tstyle = TableStyle(name='results-table', borderColour=Colour('black'), borderWidth=1.0, gridColour=Colour('black'), gridWidth=0.5, verticalAlignment=VerticalAlignment.CENTER, headingStyle=hstyle) table = DG.Table(data, heading=heading, repeat_heading=True, colWidths=col_widths, style=tstyle) doc.append(table) filename = str(self.options.ticket_results_output_name()) self.doc_gen.render(filename, doc, Progress())
def generate_tickets_pdf(self, cards: List[BingoTicket]) -> None: """generate a PDF file containing all the Bingo tickets""" doc = DG.Document(pagesize=PageSizes.A4, title=f'{self.options.game_id} - {self.options.title}', topMargin="0.15in", rightMargin="0.15in", bottomMargin="0.15in", leftMargin="0.15in") page: int = 1 num_cards: int = len(cards) cards_per_page: int = 3 if self.options.rows == 2: cards_per_page = 4 elif self.options.rows > 3: cards_per_page = 2 id_style = self.TEXT_STYLES['ticket-id'] title_style = id_style.replace('ticket-title', alignment=HorizontalAlignment.LEFT) for count, card in enumerate(cards, start=1): self.progress.text = f'Card {count}/{num_cards}' self.progress.pct = 100.0 * float(count) / float(num_cards) self.render_bingo_ticket(card, doc) data: List[DG.TableRow] = [[ DG.Paragraph(self.options.title, title_style), DG.Paragraph( f"{self.options.game_id} / T{card.ticket_number} / P{page}", id_style), ]] tstyle = TableStyle(name='ticket-id', borderWidth=0, gridWidth=0, verticalAlignment=VerticalAlignment.CENTER) table = DG.Table( data, colWidths=[Dimension(80), Dimension(80)], rowHeights=[Dimension(f'16pt')], style=tstyle) doc.append(table) if count % cards_per_page != 0: doc.append( DG.HorizontalLine('hline', width="100%", thickness="1px", colour=Colour('gray'), dash=[2, 2])) doc.append(DG.Spacer(width=0, height="0.08in")) else: doc.append(DG.PageBreak()) page += 1 filename = str(self.options.bingo_tickets_output_name()) self.doc_gen.render(filename, doc, Progress())
def render_bingo_ticket(self, card: BingoTicket, doc: DG.Document) -> None: """ Render a Bingo ticket into the specified Document. Each ticket has the Music Bingo logo followed by a table. """ doc.append(self.options.palette.logo_image("6.2in")) pstyle = self.TEXT_STYLES['ticket-cell'] data: List[DG.TableRow] = [] ranges: List[Tuple[int, int]] = [] for row_index in range(self.options.rows): ranges.append((row_index * self.options.columns, (1 + row_index) * self.options.columns)) for start, end in ranges: row: DG.TableRow = [] for index in range(start, end): items: List[DG.Paragraph] = [ DG.Paragraph(card.card_tracks[index].title, pstyle), ] if self.should_include_artist(card.card_tracks[index]): items.append( DG.Paragraph(f'<b>{card.card_tracks[index].artist}</b>', pstyle)) row.append(items) data.append(row) column_width = Dimension("1.54in") if self.options.columns > 5: column_width *= 5.0 / float(self.options.columns) row_height = Dimension("0.97in") tstyle = TableStyle(name='bingo-card', borderColour=Colour('black'), borderWidth=2.0, gridColour=Colour('black'), gridWidth=0.5, verticalAlignment=VerticalAlignment.CENTER) col_widths: List[Dimension] = [column_width] * self.options.columns row_heights: List[Dimension] = [row_height] * self.options.rows table = DG.Table(data, colWidths=col_widths, rowHeights=row_heights, style=tstyle) for box_row in range(0, self.options.rows): for box_col in range(0, self.options.columns): table.style_cells( DG.CellPos(col=box_col, row=box_row), DG.CellPos(col=box_col, row=box_row), background=card.box_colour_style(box_col, box_row)) doc.append(table)
def generate_track_listing(self, tracks: List[Song]) -> None: """generate a PDF version of the track order in the game""" assert len(tracks) > 0 doc = DG.Document( PageSizes.A4, topMargin="0.25in", bottomMargin="0.25in", leftMargin="0.35in", rightMargin="0.35in", title=f'{self.options.game_id} - {self.options.title}') doc.append(self.options.palette.logo_image("6.2in")) doc.append(DG.Spacer(width=0, height="0.05in")) doc.append( DG.Paragraph( f'Track Listing For Game Number: <b>{self.options.game_id}</b>', self.TEXT_STYLES['track-heading'])) doc.append( DG.Paragraph(self.options.title, self.TEXT_STYLES['track-title'])) cell_style = self.TEXT_STYLES['track-cell'] heading: DG.TableRow = [ DG.Paragraph('<b>Order</b>', cell_style), DG.Paragraph('<b>Title</b>', cell_style), DG.Paragraph('<b>Artist</b>', cell_style), DG.Paragraph('<b>Start Time</b>', cell_style), DG.Paragraph('', cell_style), ] data: List[DG.TableRow] = [] for index, song in enumerate(tracks, start=1): order = DG.Paragraph(f'<b>{index}</b>', cell_style) title = DG.Paragraph(song.title, cell_style) if self.should_include_artist(song): artist = DG.Paragraph(song.artist, cell_style) else: artist = DG.Paragraph('', cell_style) start = DG.Paragraph( Duration(song.start_time).format(), cell_style) end_box = DG.Paragraph('', cell_style) data.append([order, title, artist, start, end_box]) col_widths = [ Dimension("0.55in"), Dimension("2.9in"), Dimension("2.9in"), Dimension("0.85in"), Dimension("0.2in") ] hstyle = cell_style.replace(name='track-table-heading', background=self.options.palette.title_bg) tstyle = TableStyle(name='track-table', borderColour=Colour('black'), borderWidth=1.0, gridColour=Colour('black'), gridWidth=0.5, verticalAlignment=VerticalAlignment.CENTER, headingStyle=hstyle) table = DG.Table(data, heading=heading, repeat_heading=True, colWidths=col_widths, style=tstyle) doc.append(table) filename = str(self.options.track_listing_output_name()) self.doc_gen.render(filename, doc, Progress())
def test_render_bingo_ticket_table(self, mock_table): """ Test converting a documentgenerator Table to a reportlab table. This test uses example data from the Bingo ticket generator """ cstyle = DG.ElementStyle('ticket-cell', colour='black', alignment=HorizontalAlignment.CENTER, fontSize=12, leading=12, padding=Padding(bottom=4)) data: List[DG.TableRow] = [] for start, end in ((0, 5), (5, 10), (10, 15)): row: DG.TableRow = [] for index in range(start, end): items: List[DG.TableCell] = [ DG.Paragraph(f'Title {index}', cstyle), DG.Paragraph(f'<b>Artist {index}</b>', cstyle), ] row.append(items) data.append(row) column_width = Dimension("1.54in") row_height = Dimension("1.0in") col_widths = (column_width, column_width, column_width, column_width, column_width) row_heights = (row_height, row_height, row_height) tstyle = DG.TableStyle(name='bingo-ticket', borderColour=Colour('black'), borderWidth=1.0, colour='black', gridColour=Colour('black'), gridWidth=0.5, padding=Padding(0, 0, 0, 0), alignment=HorizontalAlignment.CENTER, verticalAlignment=VerticalAlignment.CENTER) dg_table = DG.Table(data, colWidths=col_widths, rowHeights=row_heights, style=tstyle) box_colours: List[Colour] = [] for name, value in CSS_COLOUR_NAMES.items(): if name == 'black': continue box_colours.append(Colour(value)) expected_styles = [ ('ALIGN', (0, 0), (-1, -1), 'CENTER'), ('BOX', (0, 0), (-1, -1), 1.0, lib.colors.black), ('BOTTOMPADDING', (0, 0), (-1, -1), 0), ('FONTSIZE', (0, 0), (-1, -1), tstyle.font_size), ('GRID', (0, 0), (-1, -1), 0.5, lib.colors.black), ('LEADING', (0, 0), (-1, -1), tstyle.leading), ('LEFTPADDING', (0, 0), (-1, -1), 0), ('RIGHTPADDING', (0, 0), (-1, -1), 0), ('TEXTCOLOR', (0, 0), (-1, -1), lib.colors.black), ('TOPPADDING', (0, 0), (-1, -1), 0), ('VALIGN', (0, 0), (-1, -1), 'CENTER'), ] index = 0 for box_row in range(0, 3): for box_col in range(0, 5): box_style = DG.ElementStyle( name=f'bingo-cell-r{box_row}-c{box_col}', alignment=HorizontalAlignment.CENTER, background=box_colours[index % len(box_colours)]) dg_table.style_cells(DG.CellPos(col=box_col, row=box_row), DG.CellPos(col=box_col, row=box_row), box_style) expected_styles.append(( 'BACKGROUND', (box_col, box_row), (box_col, box_row), lib.colors.HexColor(box_style.background.css()), )) index += 1 pdfgen = PDFGenerator() pdf_styles = pdfgen.translate_table_style(dg_table, first_row=0, last_row=len(data), num_cols=5) self.assertStyleListsEqual(expected_styles, pdf_styles) pdfgen.render_table(dg_table) _, args, kwargs = mock_table.mock_calls[0] self.assertListsEqual(data, args[0]) self.assertStyleListsEqual(expected_styles, kwargs['style']) for dg_col, pdf_col in zip(col_widths, kwargs['colWidths']): self.assertAlmostEqual(dg_col.points(), pdf_col) for dg_row, pdf_row in zip(row_heights, kwargs['rowHeights']): self.assertAlmostEqual(dg_row.points(), pdf_row)
def test_render_results_table(self, mock_table): """ Test converting a documentgenerator Table to a reportlab table. This test uses example data from the ticket results table """ cstyle = DG.ElementStyle(name='results-cell', colour=Colour('black'), alignment=HorizontalAlignment.CENTER, fontSize=10, leading=10) heading: DG.TableRow = [ DG.Paragraph('<b>Ticket Number</b>', cstyle), DG.Paragraph('<b>Wins after track</b>', cstyle), DG.Paragraph('<b>Start Time</b>', cstyle), ] data: List[DG.TableRow] = [ [ DG.Paragraph('1', cstyle), DG.Paragraph('2', cstyle), DG.Paragraph('3:45', cstyle), ], [ DG.Paragraph('2', cstyle), DG.Paragraph('3', cstyle), DG.Paragraph('4:45', cstyle), ], [ DG.Paragraph('3', cstyle), DG.Paragraph('1', cstyle), DG.Paragraph('1:45', cstyle), ], ] hstyle = DG.ElementStyle(name='heading', fontSize=12, background=Colour('blue')) col_widths: List[Dimension] = list(map(Dimension, [20, 125, 20])) tstyle = DG.TableStyle(name='test-table', borderColour=Colour('black'), borderWidth=1.0, gridColour=Colour('black'), gridWidth=0.5, alignment=HorizontalAlignment.LEFT, verticalAlignment=VerticalAlignment.CENTER, headingStyle=hstyle) dg_table = DG.Table(data, heading=heading, repeat_heading=True, colWidths=col_widths, style=tstyle) pdfgen = PDFGenerator() expected_styles = [ # table styles: ('ALIGN', (0, 0), (-1, -1), 'LEFT'), ('BOX', (0, 0), (-1, -1), 1, lib.colors.black), ('FONTSIZE', (0, 0), (-1, -1), tstyle.font_size), ('GRID', (0, 0), (-1, -1), 0.5, lib.colors.black), ('LEADING', (0, 0), (-1, -1), tstyle.leading), ('VALIGN', (0, 0), (-1, -1), 'CENTER'), # heading row styles: ('BACKGROUND', (0, 0), (2, 0), lib.colors.blue), ('FONTSIZE', (0, 0), (2, 0), hstyle.font_size), ] pdf_styles = pdfgen.translate_table_style(dg_table, first_row=1, last_row=(len(data) + 1), num_cols=3) self.assertStyleListsEqual(expected_styles, pdf_styles) pdfgen.render_table(dg_table) _, args, kwargs = mock_table.mock_calls[0] self.assertListsEqual([heading] + data, args[0]) self.assertStyleListsEqual(expected_styles, kwargs['style']) for dg_col, pdf_col in zip(col_widths, kwargs['colWidths']): self.assertAlmostEqual(dg_col.points(), pdf_col)