class PDFGenerator(ReportGenerator): """This is a generator to output a PDF using ReportLab library with preference by its Platypus API""" filename = None _is_first_page = True _is_latest_page = True _current_top_position = 0 _current_page_number = 0 _current_object = None def __init__(self, report, filename): super(PDFGenerator, self).__init__(report) self.filename = filename def execute(self): """Generate a PDF file using ReportLab pdfgen package.""" # Initializes the PDF canvas self.start_pdf(self.filename) self.generate_pages() # Finalizes the canvas self.canvas.save() def start_pdf(self, filename): """Initializes the PDF document with some properties and methods""" # Sets the PDF canvas self.canvas = Canvas(filename=filename, pagesize=self.report.page_size) # Set PDF properties self.canvas.setTitle(self.report.title) self.canvas.setAuthor(self.report.author) self._is_first_page = True def generate_band(self, band, top_position=None): """Generate a band having the current top position or informed as its top coordinate""" # Coordinates and dimensions temp_top = top_position = top_position or self.get_top_pos() band_rect = { 'left': self.report.margin_left, 'top': top_position, 'right': self.report.page_size[0] - self.report.margin_right, 'bottom': top_position - band.height, } # This should be done by a metaclass in Report domain TODO band.width = self.report.page_size[0] - self.report.margin_left - self.report.margin_right # Loop at band widgets for element in band.elements: # Widget element if isinstance(element, Widget): widget = element # Set element colors self.set_fill_color(self.report.default_font_color) # Set widget basic attributes widget.instance = self._current_object widget.generator = self widget.report = self.report # This should be done by a metaclass in Band domain TODO widget.band = band # This should be done by a metaclass in Band domain TODO if isinstance(widget, Label): para = Paragraph(widget.text, ParagraphStyle(name='Normal', **widget.style)) para.wrapOn(self.canvas, widget.width, widget.height) para.drawOn(self.canvas, self.report.margin_left + widget.left, temp_top - widget.top - para.height) # Graphic element elif isinstance(element, Graphic): graphic = element # Set element colors self.set_fill_color(graphic.fill_color or self.report.default_fill_color) self.set_stroke_color(graphic.stroke_color or self.report.default_stroke_color) self.set_stroke_width(graphic.stroke_width) if isinstance(element, RoundRect): self.canvas.roundRect( self.report.margin_left + graphic.left, top_position - graphic.top - graphic.height, graphic.width, graphic.height, graphic.radius, graphic.stroke, graphic.fill, ) elif isinstance(element, Rect): self.canvas.rect( self.report.margin_left + graphic.left, top_position - graphic.top - graphic.height, graphic.width, graphic.height, graphic.stroke, graphic.fill, ) elif isinstance(element, Line): self.canvas.line( self.report.margin_left + graphic.left, top_position - graphic.top, self.report.margin_left + graphic.right, top_position - graphic.bottom, ) elif isinstance(element, Circle): self.canvas.circle( self.report.margin_left + graphic.left_center, top_position - graphic.top_center, graphic.radius, graphic.stroke, graphic.fill, ) elif isinstance(element, Arc): self.canvas.arc( self.report.margin_left + graphic.left, top_position - graphic.top, self.report.margin_left + graphic.right, top_position - graphic.bottom, graphic.start_angle, graphic.extent, ) elif isinstance(element, Ellipse): self.canvas.ellipse( self.report.margin_left + graphic.left, top_position - graphic.top, self.report.margin_left + graphic.right, top_position - graphic.bottom, graphic.stroke, graphic.fill, ) elif isinstance(element, Image): self.canvas.drawInlineImage( graphic.image, self.report.margin_left + graphic.left, top_position - graphic.top - graphic.height, graphic.width, graphic.height, ) # Band borders if band.borders.get('all', None): self.canvas.rect( band_rect['left'], band_rect['top'] - band.height, band_rect['right'] - band_rect['left'], band.height, ) if band.borders.get('top', None): self.canvas.line(band_rect['left'], band_rect['top'], band_rect['right'], band_rect['top']) if band.borders.get('right', None): self.canvas.line(band_rect['right'], band_rect['top'], band_rect['right'], band_rect['bottom']) if band.borders.get('bottom', None): self.canvas.line(band_rect['left'], band_rect['bottom'], band_rect['right'], band_rect['bottom']) if band.borders.get('left', None): self.canvas.line(band_rect['left'], band_rect['top'], band_rect['left'], band_rect['bottom']) def generate_begin(self): """Generate the report begin band if it exists""" if not self.report.band_begin: return # Call method that print the band area and its widgets self.generate_band(self.report.band_begin) # Update top position after this band self.update_top_pos(self.report.band_begin.height) def generate_summary(self): """Generate the report summary band if it exists""" if not self.report.band_summary: return # Check to force new page if there is no available space force_new_page = self.get_available_height() < self.report.band_summary.height if force_new_page: # Ends the current page self._current_top_position = 0 self.canvas.showPage() # Starts a new one self.start_new_page() # Call method that print the band area and its widgets self.generate_band(self.report.band_summary) if force_new_page: self.generate_page_footer() def generate_page_header(self): """Generate the report page header band if it exists""" if not self.report.band_page_header: return # Call method that print the band area and its widgets self.generate_band( self.report.band_page_header, self.report.page_size[1] - self.report.margin_top ) def generate_page_footer(self): """Generate the report page footer band if it exists""" if not self.report.band_page_footer: return # Call method that print the band area and its widgets self.generate_band( self.report.band_page_footer, self.report.margin_bottom + self.report.band_page_footer.height, ) def generate_pages(self): """Loops into the queryset to create the report pages until the end""" # Preparing local auxiliar variables self._current_page_number = 0 self._current_object_index = 0 objects = self.report.queryset and \ [object for object in self.report.queryset] or\ [] # Empty report if self.report.print_if_empty and not objects: self.start_new_page() self.generate_begin() self.end_current_page() # Loop for pages while self._current_object_index < len(objects): # Starts a new page and generates the page header band self.start_new_page() # Generate the report begin band if self._current_page_number == 0: self.generate_begin() # Does generate objects if there is no details band if not self.report.band_detail: self._current_object_index = len(objects) # Loop for objects to go into grid on current page while self._current_object_index < len(objects): # Get current object from list self._current_object = objects[self._current_object_index] # Generates the detail band self.generate_band(self.report.band_detail) # Updates top position self.update_top_pos(self.report.band_detail.height) # Next object self._current_object_index += 1 # Break is this is the end of this page if self.get_available_height() < self.report.band_detail.height: break # Sets this is the latest page or not self._is_latest_page = self._current_object_index >= len(objects) # Ends the current page, printing footer and summary and necessary self.end_current_page() # Breaks if this is the latest item if self._is_latest_page: break # Increment page number self._current_page_number += 1 def start_new_page(self, with_header=True): """Do everything necessary to be done to start a new page""" if with_header: self.generate_page_header() def end_current_page(self): """Closes the current page, using showPage method. Everything done after this will draw into a new page. Before this, using the generate_page_footer method to draw the footer""" self.generate_page_footer() if self._is_latest_page: self.generate_summary() self.canvas.showPage() self._current_page_number += 1 self._is_first_page = False self.update_top_pos(set=0) # <---- update top position def get_top_pos(self): """Since the coordinates are bottom-left on PDF, we have to use this to get the current top position, considering also the top margin.""" ret = self.report.page_size[1] - self.report.margin_top - self._current_top_position if self.report.band_page_header: ret -= self.report.band_page_header.height return ret def get_available_height(self): """Returns the available client height area from the current top position until the end of page, considering the bottom margin.""" ret = self.report.page_size[1] - self.report.margin_bottom -\ self.report.margin_top - self._current_top_position if self.report.band_page_header: ret -= self.report.band_page_header.height if self.report.band_page_footer: ret -= self.report.band_page_footer.height return ret def update_top_pos(self, increase=0, decrease=0, set=None): """Updates the current top position controller, increasing (by default), decreasing or setting it with a new value.""" if set is not None: self._current_top_position = set else: self._current_top_position += increase self._current_top_position -= decrease return self._current_top_position def get_page_count(self): # TODO """Calculate and returns the page count for this report. The challenge here is do this calculate before to generate the pages.""" pass def set_fill_color(self, color): """Sets the current fill on canvas. Used for fonts and shape fills""" self.canvas.setFillColor(color) def set_stroke_color(self, color): """Sets the current stroke on canvas""" self.canvas.setStrokeColor(color) def set_stroke_width(self, width): """Sets the stroke/line width for shapes""" self.canvas.setLineWidth(width)
class PDFGenerator(ReportGenerator): """This is a generator to output a PDF using ReportLab library with preference by its Platypus API""" filename = None _is_first_page = True _is_latest_page = True _current_top_position = 0 _current_page_number = 0 _current_object = None def __init__(self, report, filename): super(PDFGenerator, self).__init__(report) self.filename = filename def execute(self): """Generate a PDF file using ReportLab pdfgen package.""" # Initializes the PDF canvas self.start_pdf(self.filename) self.generate_pages() # Finalizes the canvas self.canvas.save() def start_pdf(self, filename): """Initializes the PDF document with some properties and methods""" # Sets the PDF canvas self.canvas = Canvas(filename=filename, pagesize=self.report.page_size) # Set PDF properties self.canvas.setTitle(self.report.title) self.canvas.setAuthor(self.report.author) self._is_first_page = True def generate_band(self, band, top_position=None): """Generate a band having the current top position or informed as its top coordinate""" # Coordinates and dimensions temp_top = top_position = top_position or self.get_top_pos() band_rect = { 'left': self.report.margin_left, 'top': top_position, 'right': self.report.page_size[0] - self.report.margin_right, 'bottom': top_position - band.height, } # This should be done by a metaclass in Report domain TODO band.width = self.report.page_size[ 0] - self.report.margin_left - self.report.margin_right # Loop at band widgets for element in band.elements: # Widget element if isinstance(element, Widget): widget = element # Set element colors self.set_fill_color(self.report.default_font_color) # Set widget basic attributes widget.instance = self._current_object widget.generator = self widget.report = self.report # This should be done by a metaclass in Band domain TODO widget.band = band # This should be done by a metaclass in Band domain TODO if isinstance(widget, Label): para = Paragraph( widget.text, ParagraphStyle(name='Normal', **widget.style)) para.wrapOn(self.canvas, widget.width, widget.height) para.drawOn(self.canvas, self.report.margin_left + widget.left, temp_top - widget.top - para.height) # Graphic element elif isinstance(element, Graphic): graphic = element # Set element colors self.set_fill_color(graphic.fill_color or self.report.default_fill_color) self.set_stroke_color(graphic.stroke_color or self.report.default_stroke_color) self.set_stroke_width(graphic.stroke_width) if isinstance(element, RoundRect): self.canvas.roundRect( self.report.margin_left + graphic.left, top_position - graphic.top - graphic.height, graphic.width, graphic.height, graphic.radius, graphic.stroke, graphic.fill, ) elif isinstance(element, Rect): self.canvas.rect( self.report.margin_left + graphic.left, top_position - graphic.top - graphic.height, graphic.width, graphic.height, graphic.stroke, graphic.fill, ) elif isinstance(element, Line): self.canvas.line( self.report.margin_left + graphic.left, top_position - graphic.top, self.report.margin_left + graphic.right, top_position - graphic.bottom, ) elif isinstance(element, Circle): self.canvas.circle( self.report.margin_left + graphic.left_center, top_position - graphic.top_center, graphic.radius, graphic.stroke, graphic.fill, ) elif isinstance(element, Arc): self.canvas.arc( self.report.margin_left + graphic.left, top_position - graphic.top, self.report.margin_left + graphic.right, top_position - graphic.bottom, graphic.start_angle, graphic.extent, ) elif isinstance(element, Ellipse): self.canvas.ellipse( self.report.margin_left + graphic.left, top_position - graphic.top, self.report.margin_left + graphic.right, top_position - graphic.bottom, graphic.stroke, graphic.fill, ) elif isinstance(element, Image): self.canvas.drawInlineImage( graphic.image, self.report.margin_left + graphic.left, top_position - graphic.top - graphic.height, graphic.width, graphic.height, ) # Band borders if band.borders.get('all', None): self.canvas.rect( band_rect['left'], band_rect['top'] - band.height, band_rect['right'] - band_rect['left'], band.height, ) if band.borders.get('top', None): self.canvas.line(band_rect['left'], band_rect['top'], band_rect['right'], band_rect['top']) if band.borders.get('right', None): self.canvas.line(band_rect['right'], band_rect['top'], band_rect['right'], band_rect['bottom']) if band.borders.get('bottom', None): self.canvas.line(band_rect['left'], band_rect['bottom'], band_rect['right'], band_rect['bottom']) if band.borders.get('left', None): self.canvas.line(band_rect['left'], band_rect['top'], band_rect['left'], band_rect['bottom']) def generate_begin(self): """Generate the report begin band if it exists""" if not self.report.band_begin: return # Call method that print the band area and its widgets self.generate_band(self.report.band_begin) # Update top position after this band self.update_top_pos(self.report.band_begin.height) def generate_summary(self): """Generate the report summary band if it exists""" if not self.report.band_summary: return # Check to force new page if there is no available space force_new_page = self.get_available_height( ) < self.report.band_summary.height if force_new_page: # Ends the current page self._current_top_position = 0 self.canvas.showPage() # Starts a new one self.start_new_page() # Call method that print the band area and its widgets self.generate_band(self.report.band_summary) if force_new_page: self.generate_page_footer() def generate_page_header(self): """Generate the report page header band if it exists""" if not self.report.band_page_header: return # Call method that print the band area and its widgets self.generate_band(self.report.band_page_header, self.report.page_size[1] - self.report.margin_top) def generate_page_footer(self): """Generate the report page footer band if it exists""" if not self.report.band_page_footer: return # Call method that print the band area and its widgets self.generate_band( self.report.band_page_footer, self.report.margin_bottom + self.report.band_page_footer.height, ) def generate_pages(self): """Loops into the queryset to create the report pages until the end""" # Preparing local auxiliar variables self._current_page_number = 0 self._current_object_index = 0 objects = self.report.queryset and \ [object for object in self.report.queryset] or\ [] # Empty report if self.report.print_if_empty and not objects: self.start_new_page() self.generate_begin() self.end_current_page() # Loop for pages while self._current_object_index < len(objects): # Starts a new page and generates the page header band self.start_new_page() # Generate the report begin band if self._current_page_number == 0: self.generate_begin() # Does generate objects if there is no details band if not self.report.band_detail: self._current_object_index = len(objects) # Loop for objects to go into grid on current page while self._current_object_index < len(objects): # Get current object from list self._current_object = objects[self._current_object_index] # Generates the detail band self.generate_band(self.report.band_detail) # Updates top position self.update_top_pos(self.report.band_detail.height) # Next object self._current_object_index += 1 # Break is this is the end of this page if self.get_available_height( ) < self.report.band_detail.height: break # Sets this is the latest page or not self._is_latest_page = self._current_object_index >= len(objects) # Ends the current page, printing footer and summary and necessary self.end_current_page() # Breaks if this is the latest item if self._is_latest_page: break # Increment page number self._current_page_number += 1 def start_new_page(self, with_header=True): """Do everything necessary to be done to start a new page""" if with_header: self.generate_page_header() def end_current_page(self): """Closes the current page, using showPage method. Everything done after this will draw into a new page. Before this, using the generate_page_footer method to draw the footer""" self.generate_page_footer() if self._is_latest_page: self.generate_summary() self.canvas.showPage() self._current_page_number += 1 self._is_first_page = False self.update_top_pos(set=0) # <---- update top position def get_top_pos(self): """Since the coordinates are bottom-left on PDF, we have to use this to get the current top position, considering also the top margin.""" ret = self.report.page_size[ 1] - self.report.margin_top - self._current_top_position if self.report.band_page_header: ret -= self.report.band_page_header.height return ret def get_available_height(self): """Returns the available client height area from the current top position until the end of page, considering the bottom margin.""" ret = self.report.page_size[1] - self.report.margin_bottom -\ self.report.margin_top - self._current_top_position if self.report.band_page_header: ret -= self.report.band_page_header.height if self.report.band_page_footer: ret -= self.report.band_page_footer.height return ret def update_top_pos(self, increase=0, decrease=0, set=None): """Updates the current top position controller, increasing (by default), decreasing or setting it with a new value.""" if set is not None: self._current_top_position = set else: self._current_top_position += increase self._current_top_position -= decrease return self._current_top_position def get_page_count(self): # TODO """Calculate and returns the page count for this report. The challenge here is do this calculate before to generate the pages.""" pass def set_fill_color(self, color): """Sets the current fill on canvas. Used for fonts and shape fills""" self.canvas.setFillColor(color) def set_stroke_color(self, color): """Sets the current stroke on canvas""" self.canvas.setStrokeColor(color) def set_stroke_width(self, width): """Sets the stroke/line width for shapes""" self.canvas.setLineWidth(width)
class pdf: def __init__(self, path: str, name: str = 'generated'): """ Create a pdf-file object\n :param path: path to create file :param name: name of file """ self.file = Canvas(self._get_path(path, name)) self.set_font(12) self.page = 1 def _get_path(self, path: str, name: str = 'generated') -> str: """ This function cleans path\n :param path: path to create file :param name: name of file :return: clean path to file """ path = ''.join(symbol for symbol in path.lower() if symbol not in ' <>?"\*') while path.count(':') > 1: path = path[:path.rfind(':')] + path[path.rfind(':') + 1:] while path[len(path) - 1] == ' ': path = path[:len(path) - 1] if ".pdf" in path: path = path[0:path.rfind('/') + 1:1] if path[len(path) - 1] != '/': path += '/' if '.pdf' in name: name = name[:name.rfind('.')] return path + name + '.pdf' def _format_data(self, data: dict) -> list: """ This function processing data and return list of data for create table\n :param data: dict of data :return: list of data """ new_data = [[data['title']]] add_list = [] value_list = [] for column_elem in data['columns']: add_list.append(column_elem['name']) value_list.append(column_elem['value']) new_data.append(add_list.copy()) add_list.clear() for row_elem in data['rows']: for value in value_list: add_list.append(row_elem[value]) new_data.append(add_list.copy()) add_list.clear() return new_data def _normal_color(self): self.file.setFillColor('black') self.file.setStrokeColor('black') def set_font(self, font_size: int): """ This function set up font and his size in file\n :param font_size: size of font """ self.font_size = font_size using_font = ttfonts.TTFont("Calibri", "Calibri.ttf") pdfmetrics.registerFont(using_font) self.file.setFont("Calibri", self.font_size) def write_text(self, text: str, position: str = "mid", x: int = 297, y: int = 815): """" This function write text on defined position\n size of page is 595,841\n :param text: string of text to writing :param position: left/mid/right position of string of text :param x, y: coordinates of string """ self._normal_color() if position == "left": self.file.drawString(x, y, text) elif position == "mid": self.file.drawCentredString(x, y, text) elif position == "right": self.file.drawRightString(x, y, text) def random_drawing(self, fg_count: int): """ This function draws random picture\n :param fg_count: count of figures, drawn on page """ for figure in range(fg_count): methods = [ self.file.bezier(randint(150, 495), randint(150, 741), randint(150, 495), randint(150, 741), randint(150, 495), randint(150, 741), randint(150, 495), randint(150, 741)), self.file.arc(randint(100, 495), randint(100, 741), randint(100, 495), randint(100, 741)), self.file.rect(randint(100, 395), randint(100, 641), randint(1, 100), randint(1, 100), fill=randint(0, 1)), self.file.ellipse(randint(100, 495), randint(100, 741), randint(100, 495), randint(100, 741), fill=randint(0, 1)), self.file.circle(randint(100, 395), randint(100, 641), randint(1, 100), fill=randint(0, 1)), self.file.roundRect(randint(100, 395), randint(100, 641), randint(1, 100), randint(1, 100), randint(1, 100), fill=randint(0, 1)) ] self.file.setFillColorRGB(uniform(0, 1), uniform(0, 1), uniform(0, 1), alpha=uniform(0, 1)) self.file.setStrokeColorRGB(uniform(0, 1), uniform(0, 1), uniform(0, 1), alpha=uniform(0, 1)) choice(methods) def draw_table(self, data: dict, x: int = 10, y: int = 10): """ This function draws table from your dictionary of data\n size of page is 595.27,841.89\n :param data: dictionary with data, e.g. :param x, y: coordinates of left-bottom corner of table { 'title': 'Table title', 'columns': [ {'name': 'Name', 'value': 'name'}, {'name': 'Age', 'value': 'age'} ], 'rows': [ {'name': 'string1', 'age': 23}, {'name': 'string2', 'age': 43} ] } """ self._normal_color() data = self._format_data(data) table = Table(data=data, style=[("GRID", (0, 1), (-1, -1), 1, "Black"), ("FONT", (0, 0), (-1, -1), "Calibri", self.font_size), ("BOX", (0, 0), (-1, -1), 1, "Black")]) table.wrapOn(self.file, 10, 10) table.drawOn(self.file, x, y) def insert_image(self, path: str, x: int = 100, y: int = 200, width: int = None, height: int = None): """ This function inserts image in pdf-file\n size of page is 595.27,841.89\n :param path: path to image :param x, y: coordinates of left-bottom corner of image :param width, height: sizes of image """ image = Image(path, width, height) image.drawOn(self.file, x, y) def next_page(self): """ This function turns the page\n """ self._normal_color() self.file.drawString(565, 30, str(self.page)) self.page += 1 self.file.showPage() def save(self, author: str = 'pdf_gen', title: str = 'GENERATED'): """ This function saves our file\n :param author: author of file :param title: title of file """ self._normal_color() self.file.drawString(565, 30, str(self.page)) self.file.setAuthor(author) self.file.setTitle(title) self.file.save()