def __init__(self, name: str, width: RelaxedDimension, height: RelaxedDimension, colour: Colour): super(Box, self).__init__(ElementStyle(name=name, colour=colour)) if not isinstance(name, str): raise ValueError(f"Invalid name: {name}") self.width = Dimension(width) self.height = Dimension(height)
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 logo_image(self, width: RelaxedDimension) -> Image: """filename of the Music Bingo logo""" width = Dimension(width) #pylint: disable=no-member filename, img_width, img_height = self.value.logo aspect: float = float(img_width) / float(img_height) width = Dimension(width) return Image(filename=Assets.get_data_filename(filename), width=width, height=(width / aspect))
def __init__(self, filename: Path, width: RelaxedDimension, height: RelaxedDimension): super(Image, self).__init__(None) if not isinstance(filename, Path): cls = type(filename) raise ValueError(f"Invalid filename class : {cls}") self.filename = filename self.width = Dimension(width) self.height = Dimension(height)
def __init__(self, top: RelaxedDimension = 0, right: RelaxedDimension = 0, bottom: RelaxedDimension = 0, left: RelaxedDimension = 0): self.top = Dimension(top) self.right = Dimension(right) self.bottom = Dimension(bottom) self.left = Dimension(left)
def test_render_image(self): """test rendering DG.Image to a reportlab image""" filename, width, height = ('logo_banner.jpg', 7370 / 50.0, 558 / 50.0) ext_filename = self.extra_files / filename dg_img = DG.Image(ext_filename, width=width, height=height) pdfgen = PDFGenerator() pdf_img = pdfgen.render_image(dg_img) self.assertEqual(pdf_img.filename, str(dg_img.filename)) self.assertAlmostEqual(pdf_img.drawWidth, Dimension(width).points()) self.assertAlmostEqual(pdf_img.drawHeight, Dimension(height).points())
def test_render_horiz_line(self): """test rendering DG.HorizontalLine to a reportlab line""" dg_line = DG.HorizontalLine(name='hr', width="5in", colour='blue', thickness=2) pdfgen = PDFGenerator() pdf_line = pdfgen.render_horiz_line(dg_line) self.assertAlmostEqual(pdf_line.width, Dimension("5in").points()) self.assertAlmostEqual(pdf_line.lineWidth, Dimension(2).points())
def __init__(self, pagesize: PageSizes, topMargin: RelaxedDimension = INCH, bottomMargin: RelaxedDimension = INCH, leftMargin: RelaxedDimension = INCH, rightMargin: RelaxedDimension = INCH, title: Optional[str] = None): self.pagesize = pagesize self.top_margin = Dimension(topMargin) self.bottom_margin = Dimension(bottomMargin) self.left_margin = Dimension(leftMargin) self.right_margin = Dimension(rightMargin) self.title = title self._elements: List[Element] = []
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_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 test_render_empty_document(self): """test rendering DG.Document to a reportlab document""" tmpfile = os.path.join(self.tmpdir, "test.pdf") dg_doc = DG.Document(pagesize=PageSizes.A4, topMargin=0.0, bottomMargin=0.0, leftMargin=10, rightMargin=20, title="Empty document") pdfgen = PDFGenerator() pdf_doc = pdfgen.render_document(tmpfile, dg_doc) self.assertEqual(pdf_doc.topMargin, 0) self.assertEqual(pdf_doc.bottomMargin, 0) self.assertAlmostEqual(pdf_doc.leftMargin, Dimension(10).points()) self.assertAlmostEqual(pdf_doc.rightMargin, Dimension(20).points()) self.assertAlmostEqual(pdf_doc.pagesize[0], PageSizes.A4.width().points()) self.assertAlmostEqual(pdf_doc.pagesize[1], PageSizes.A4.height().points()) self.assertEqual(pdf_doc.title, dg_doc.title)
def __init__(self, name: str, width: RelaxedDimension, thickness: RelaxedDimension, colour: Colour, dash: Optional[List[RelaxedDimension]] = None): """ width - length of line thickness - thickness of line colour - colour of line dash - array of lengths used to specify length of each dash and the gap between each dash """ super(HorizontalLine, self).__init__(ElementStyle(name=name, colour=colour)) if not isinstance(name, str): raise ValueError(f"Invalid name: {name}") self.width = Dimension(width) self.thickness = Dimension(thickness) self.dash: Optional[List[Dimension]] = None if dash is not None: self.dash = [Dimension(w) for w in dash]
def __init__(self, name: str, text: str, size: RelaxedDimension, style: ElementStyle, borderColour: Optional[Union[Colour, str]] = None, borderWidth: float = 1.0): super(Checkbox, self).__init__(style) self.name = name self.text = text self.size = Dimension(size) self.border_colour = borderColour self.border_width = borderWidth
def test_render_box(self, mock_table): """test rendering DG.Box to a reportlab table""" dg_box = DG.Box('test-box', width="10in", height=0, colour='black') pdfgen = PDFGenerator() pdfgen.render_box(dg_box) _, args, kwargs = mock_table.mock_calls[0] col_width = Dimension("10.0in").points() expected_styles = [("LINEBELOW", (0, 0), (-1, -1), 1, lib.colors.black) ] self.assertEqual(len(args), 1) # one positonal argument (data) self.assertEqual(len(args[0]), 1) # data has one row self.assertEqual(len(args[0][0]), 1) # row has one cell self.assertEqual(args[0][0][0], "") self.assertStyleListsEqual(expected_styles, kwargs['style']) self.assertEqual(len(kwargs['colWidths']), 1) self.assertAlmostEqual(col_width, kwargs['colWidths'][0]) self.assertEqual(len(kwargs['rowHeights']), 1) self.assertAlmostEqual(0.0, kwargs['rowHeights'][0])
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 __init__(self, width: RelaxedDimension, height: RelaxedDimension): super(Spacer, self).__init__(None) self.width = Dimension(width) self.height = Dimension(height)
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)