class PdfDocument(object): CENTER = 'center' RIGHT = 'right' LEFT = 'left' ALIGN = { CENTER: 'Centred', RIGHT: 'Right' } def __init__(self, instream, outstream, font=("Takao", 9)): self.base = PdfFileReader(instream) self.s = BytesIO() self.c = Canvas(self.s, pagesize=A4) self.font = font self.c.setFont(*self.font) self.output = outstream def string(self, pos, t, align=LEFT): getattr(self.c, 'draw%sString' % self.ALIGN.get(align, ''))(*pos, t) def circle(self, pos): self.c.circle(*pos, r=10, stroke=1, fill=0) def text(self, pos, txt, **kwargs): t = self.c.beginText() t.setTextOrigin(*pos) t.textLines(txt) self.c.drawText(t) def nextpage(self): self.c.showPage() self.c.setFont(*self.font) def write(self): self.c.save() self.s.seek(0) inp = PdfFileReader(self.s) out = PdfFileWriter() for i in range(min(inp.getNumPages(), self.base.getNumPages())): p = self.base.getPage(i) p.mergePage(inp.getPage(i)) out.addPage(p) out.write(self.output)
def save_design_cards(seqs: typing.List[Seq], savedir: str, symbols_dir: str, file_basename: str, title: str): pg_w = pagesizes.A7[1] pg_h = pagesizes.A7[0] / 2 lr_pad = 5 * units.mm dot_r = .5 * units.mm dot_hpad = 2 * units.mm dot_vpad = .5 * units.mm fn = os.path.join(savedir, f'{file_basename}.pdf') canvas = Canvas(fn, pagesize=(pg_w, pg_h)) canvas.setTitle(title) for i, s in enumerate(seqs): i = i + 1 sl = len(s) row = -1 col = 0 for k in range(i): if k % 5 == 0: row += 1 col = 0 rl = min(5, i - 5 * row) c = (rl - 1) * (2 * dot_r + dot_hpad) / 2 x = pg_w / 2 - c + col * (2 * dot_r + dot_hpad) y = pg_h - dot_vpad - dot_r - row * (dot_vpad + 2 * dot_r) canvas.circle(x, y, dot_r, stroke=0, fill=1) col += 1 size = (pg_w - 2 * lr_pad) / sl canvas.grid([lr_pad + k * size for k in range(sl + 1)], [(pg_h - size) / 2, (pg_h + size) / 2]) for j, e in enumerate(s): if e is None: continue symbol_fn = os.path.join(symbols_dir, f'SSL{e + 1:02d}.svg') symbol = fit_size(svglib.svg2rlg(symbol_fn), size, size) x = lr_pad + j * size y = (pg_h - size) / 2 renderPDF.draw(symbol, canvas, x, y) canvas.showPage() canvas.save()
def test_05_coordinates(self): from reportlab.lib.pagesizes import A4 from reportlab.pdfgen.canvas import Canvas from reportlab.lib.units import inch from reportlab.lib.colors import pink, black, red, blue, green c = Canvas('demo.pdf', pagesize=A4) c.translate(inch,inch) c.setStrokeColor(pink) c.grid([1*inch,2*inch,3*inch,4*inch],[0.5*inch, 1*inch, .5*inch, 2*inch, 2.5*inch]) c.setFont("Times-Roman", 20) c.drawString(0,0, "(0,0) the Origin") c.drawString(2.5*inch, 1*inch, "(2.5,1) in inches") c.drawString(4*inch, 2.5*inch, "(4,2.5)") c.setFillColor(red) c.rect(0,2*inch,0.2*inch, 0.3*inch, fill=1) c.setFillColor(green) c.circle(4.5*inch, 0.4*inch, 0.2*inch, fill=1) c.showPage() c.save()
def test(self): outPDF = "test_compare.pdf" pageSize = pagesizes.portrait(pagesizes.A4) canv = Canvas(outPDF, pagesize=pageSize) # common variables W, H = 5 * cm, 20 * cm kifMode = "shrink" # overflow/truncate/shrink/error # hyphenated column p = HyParagraph(testdata, sth) story = [p] frame = KeepInFrame(W, H, story, mode=kifMode) w, h = frame.wrapOn(canv, W, H) x, y = 100, 100 frame.drawOn(canv, x, y + (H - h)) canv.setLineWidth(2) canv.setStrokeColor(black) canv.circle(x, y, 10) canv.setStrokeColor(red) canv.rect(x, y, W, H) canv.setLineWidth(1) canv.setStrokeColor(blue) canv.rect(x, y + (H - h), w, h) # non-hyphenated column p = Paragraph(testdata, st) story = [p] frame = KeepInFrame(W, H, story, mode=kifMode) w, h = frame.wrapOn(canv, W, H) x, y = 300, 100 frame.drawOn(canv, x, y + (H - h)) canv.setLineWidth(2) canv.setStrokeColor(black) canv.circle(x, y, 10) canv.setStrokeColor(red) canv.rect(x, y, W, H) canv.setLineWidth(1) canv.setStrokeColor(blue) canv.rect(x, y + (H - h), w, h) canv.showPage() canv.save()
def drawNode(self, pos, col): Canvas.setFillColor(self, col) Canvas.circle(self, pos[0], pos[1], 0.3, fill=1)
def main(): pdfmetrics.registerFont(TTFont('Dosis', 'Dosis-Medium.ttf')) stars = [] with open('bsc5.dat', 'rb') as f: for line in f: line = '.' + line # switch to 1-based indexing if not line[62].strip(): continue # skip coordinate-less novas letter = intern(line[8:11]) if letter == ' ': letter = None h, m, s = float(line[61:63]), float(line[63:65]), float(line[65:69]) ra = (h + (m + s / 60.0) / 60.0) * tau / 24.0 d, m, s = float(line[69:72]), float(line[72:74]), float(line[76:78]) dec = (d + (m + s / 60.0) / 60.0) * tau / 360.0 mag = float(line[103:108]) stars.append((letter, ra, dec, mag)) h, w = 48, 96 c = Canvas('logo.pdf', pagesize=(w, h)) c.setFillColor('white') c.rect(0, 0, w, h, stroke=0, fill=1) c.setFillColor(bright_star_color) rotation = 10.0 * tau / 360.0 # magscale = 0.1 # For 10 degrees: x_offset = 96 -33.5 y_offset = h +37.5 # For 15 degrees: # x_offset = 96 -28.5 # y_offset = 96 +0.5 # for 45 degrees: # x_offset = 96 -13.5 # y_offset = 96 -10 small_glyphs = [] c.setFont('Helvetica', 2) for letter, ra, dec, mag in stars: # if mag > 4.0: # continue d = - (dec - quarter_tau) * 100 ra += rotation x = d * sin(ra) y = d * cos(ra) if y < -63.0 or y > -39.0: continue if x < -43.0 or x > 19.0: continue x += x_offset y += y_offset r = ((13.0 - mag) / 10.0) ** 4.0 #* magscale r = min(r, 1.0) if r < 0.5: small_glyphs.append((x, y, r)) else: if letter is not None: c.saveState() greek_letter, offset = greeks[letter] c.setFillColor(greek_color) c.drawString(x+offset, y+0.5, greek_letter) if letter == 'Alp': c.setFillColor(alpha_star_color) c.circle(x, y, r, stroke=0, fill=1) c.restoreState() if letter != 'Alp': c.circle(x, y, r, stroke=0, fill=1) else: c.circle(x, y, r, stroke=0, fill=1) c.setFillColor(dim_star_color) for x, y, r in small_glyphs: c.circle(x, y, r, stroke=0, fill=1) c.setFillColor(text_color) #, alpha=0.5) c.setFont('Dosis', 24) sw = c.stringWidth('Skyfield') c.drawString(w // 2 - sw // 2, h - 40, 'Skyfield') c.showPage() with open('logo.pdf', 'wb') as f: f.write(c.getpdfdata())
def render(self, outfile, font_name, font_size): """ Render the binary heat map as a PDF to the file-like object or filename ``outfile``. All text will be typeset in the font named ``font_name`` at size ``font_size``. """ c = Canvas(outfile) c.setFont(font_name, font_size) leftlen = max(map(c.stringWidth, self.row_labels)) + LABEL_PAD * 2 toplen = max(map(c.stringWidth, self.column_labels)) + LABEL_PAD * 2 miny = self.rows * font_size * 1.2 maxx = self.columns * font_size * 1.2 c.setPageSize((leftlen + maxx + PADDING*2, miny + toplen + PADDING*2)) # Set coordinates so that LL corner has coord (-leftlen-PADDING, # -miny-PADDING) and the origin is at the point where the borders of the # row & column labels meet: c.translate(leftlen+PADDING, miny+PADDING) lineheight = font_size * 1.2 radius = lineheight / 3 c.setFillColorRGB(*COL_BG_COLOR) for i in range(0, self.columns, 2): c.rect( i * lineheight, -miny, lineheight, miny + toplen, stroke=0, fill=1, ) c.setFillColorRGB(*ROW_BG_COLOR) for i in range(2, self.rows+1, 2): # Yes, it starts at 2, so that the positive rectangle height will # make it fill row 1. c.rect( -leftlen, -i * lineheight, leftlen + maxx, lineheight, stroke=0, fill=1, ) c.setFillColorRGB(0, 0, 0) c.line(0, toplen, 0, -miny) c.line(-leftlen, 0, maxx, 0) for i, label in enumerate(self.row_labels): c.drawRightString( -LABEL_PAD, -(i+1) * lineheight + font_size / 3, label, ) for i, label in enumerate(self.column_labels): c.saveState() c.translate((i+1) * lineheight, 0) c.rotate(90) c.drawString(LABEL_PAD, font_size / 3, label) c.restoreState() for row, col in self.get_indexed_pairs(): c.circle( (col+0.5) * lineheight, -(row+0.5) * lineheight, radius, stroke=0, fill=1, ) c.showPage() c.save()
def overlay(exam_images, max_scores, exam_seats, output_file, code=None, paper_type=letter): can = Canvas(output_file, pagesize=paper_type) paper_width, paper_height = paper_type exam_images = [[ImageReader(image) for image in exam] for exam in exam_images] for xxx, exam_seat in enumerate(exam_seats): sys.stdout.write('\rCompiling: %d / %d' % (xxx+1, len(exam_seats))) sys.stdout.flush() for index, max_score in enumerate(max_scores): page_code = 0 if code is None else code[xxx * len(max_scores) + index] bg = exam_images[page_code][index] can.drawImage(bg, 0*cm, 0*cm, width=paper_width, height=paper_height) # Draw QR codes. qr = ImageReader(qrcode.make('%d, %d, %d' % (exam_seat.exam_num, index+1, max_score), box_size=1).get_image()) can.drawImage(qr, 0.75*cm, 0.75*cm, width=1.5*cm, height=1.5*cm) can.drawImage(qr, 19.34*cm, 0.75*cm, width=1.5*cm, height=1.5*cm) can.setFont('Times-Roman', 10) can.drawCentredString(1.5*cm, 0.6*cm, 'Ex %d' % exam_seat.exam_num) can.drawCentredString(20.09*cm, 0.6*cm, 'Ex %d' % exam_seat.exam_num) can.drawCentredString(3.4*cm, 0.6*cm, 'Page score') if index == 0: # Write seat name. if exam_seat.name: can.setFont('Times-Bold', 22) can.drawString(11.4*cm, 26.60*cm, 'Seat:') can.setFont('Times-Roman', 22) can.drawString(13.3*cm, 26.60*cm, exam_seat.name) # Draw UIN box. # US letter is 21.58cm x 27.93cm box_l, box_t = 11.79*cm, 23.825*cm box_w, box_h = 8*cm, 10*cm num_digits = 9 can.rect(box_l, box_t - box_h, box_w, box_h) can.line(box_l, box_t - 1.4*cm, box_l + box_w, box_t - 1.4*cm) xstep = box_w / (num_digits + 1) ystep = box_h / (10 + 1) for i in range(num_digits-1): can.line(box_l + 1.5*xstep + i*xstep, box_t - 1.4*cm, box_l + 1.5*xstep + i*xstep, box_t - 1.4*cm + 0.32*cm) can.setFont('Times-Bold', 12) can.drawString(box_l+0.2*cm, box_t - 0.52*cm, 'Write and bubble in your UIN:') can.setFont('Times-Roman', 10) for i in range(num_digits): for j in range(10): can.circle(box_l + 1.0*xstep + i*xstep, box_t - 1.4*cm - 0.6*cm - 0.8*j*cm, 0.3*cm) can.drawCentredString(box_l + 1.0*xstep + i*xstep, box_t - 1.4*cm - 0.12*cm - 0.6*cm - 0.8*j*cm, str(j)) # Draw score box. can.setLineWidth(1.5) can.line(0*cm, 2.3*cm, 21.59*cm, 2.3*cm) can.line(2.3*cm, 0*cm, 2.3*cm, 2.3*cm) can.line(4.5*cm, 0*cm, 4.5*cm, 2.3*cm) can.line(19.0*cm, 0*cm, 19.0*cm, 2.3*cm) can.line(4.5*cm, 0.7*cm, 19.0*cm, 0.7*cm) if max_score > 0: can.setLineWidth(0.5) for x in range(max_score+1): can.rect(5.05*cm + 1.3*x*cm, 1.3*cm, 0.4*cm, 0.4*cm) can.setFont('Times-Roman', 10) for x in range(max_score+1): can.drawCentredString(5.25*cm + 1.3*x*cm, 1.375*cm, str(x)) can.setDash(2, 3) for x in range(max_score+1): can.rect(4.85*cm + 1.3*x*cm, 1.1*cm, 0.8*cm,0.8*cm) can.showPage() can.save() sys.stdout.write('\n') sys.stdout.flush()
def render(self, outfile, font_name, font_size): """ Render the binary heat map as a PDF to the file-like object or filename ``outfile``. All text will be typeset in the font named ``font_name`` at size ``font_size``. """ c = Canvas(outfile) c.setFont(font_name, font_size) leftlen = max(map(c.stringWidth, self.row_labels)) + LABEL_PAD * 2 toplen = max(map(c.stringWidth, self.column_labels)) + LABEL_PAD * 2 miny = self.rows * font_size * 1.2 maxx = self.columns * font_size * 1.2 c.setPageSize( (leftlen + maxx + PADDING * 2, miny + toplen + PADDING * 2)) # Set coordinates so that LL corner has coord (-leftlen-PADDING, # -miny-PADDING) and the origin is at the point where the borders of the # row & column labels meet: c.translate(leftlen + PADDING, miny + PADDING) lineheight = font_size * 1.2 radius = lineheight / 3 c.setFillColorRGB(*COL_BG_COLOR) for i in range(0, self.columns, 2): c.rect( i * lineheight, -miny, lineheight, miny + toplen, stroke=0, fill=1, ) c.setFillColorRGB(*ROW_BG_COLOR) for i in range(2, self.rows + 1, 2): # Yes, it starts at 2, so that the positive rectangle height will # make it fill row 1. c.rect( -leftlen, -i * lineheight, leftlen + maxx, lineheight, stroke=0, fill=1, ) c.setFillColorRGB(0, 0, 0) c.line(0, toplen, 0, -miny) c.line(-leftlen, 0, maxx, 0) for i, label in enumerate(self.row_labels): c.drawRightString( -LABEL_PAD, -(i + 1) * lineheight + font_size / 3, label, ) for i, label in enumerate(self.column_labels): c.saveState() c.translate((i + 1) * lineheight, 0) c.rotate(90) c.drawString(LABEL_PAD, font_size / 3, label) c.restoreState() for row, col in self.get_indexed_pairs(): c.circle( (col + 0.5) * lineheight, -(row + 0.5) * lineheight, radius, stroke=0, fill=1, ) c.showPage() c.save()
def createCalendar(start, end, filename=None, size=SIZE, color=colors.red): """ Create an academic calendar for a quarter. Counts academic weeks + 1 week for finals. start: an Arrow date end: an Arrow date filename: String containing the file to write the calendar to size: size, in points of the canvas to write on color: color for accents """ # initializations canvas = Canvas(filename, size) weekHeader = calendar.weekheader(1).split() weekHeader.insert(0, 0) width, height = size # dictionary for each month in quarter months = 1 monthInfo = {} for dt in arrow.Arrow.range('month', start.replace(day=1), end.replace(day=2)): name = dt.format('MMM') cal = calendar.monthcalendar(dt.year, dt.month) monthInfo[months] = mo(name, cal) months += 1 # arrange array for printing fullCal = [] fullCal.append(weekHeader) weekNum = 1 for m in monthInfo: weekNum -= 1 fullCal.append(monthInfo[m].name) for w in monthInfo[m].cal: # add academic week numbers if (m == 1 and max(w) >= start.day) or weekNum > 0: weekNum += 1 if weekNum > NUMWEEKS + 1: weekNum = 0 w.insert(0, weekNum) fullCal.append(w) # draw everything out cellWidth = .3 * inch cellHeight = .3 * inch x = 1.5 * inch y = height - inch canvas.setStrokeAlpha(.25) for week in fullCal: # found a month if type(week) is str: canvas.setFont('Helvetica-Bold', 11) canvas.setFillColor(color) canvas.drawString(inch, y, week) canvas.setFillColor(colors.black) canvas.setFont('Helvetica', 11) else: # found an array for weekdays for d, day in enumerate(week): if day != 0: # weekday abbreviations if type(day) is str: canvas.setFont('Helvetica-Bold', 11) else: canvas.setFont('Helvetica', 11) # draw circles around all the week numbers if d == 0: canvas.circle(x, y + .05 * inch, .1 * inch) # weekends if d in (6, 7): canvas.setFillColor(color) else: canvas.setFillColor(colors.black) canvas.drawCentredString(x, y, str(day)) x += cellWidth x = 1.5 * inch y -= cellHeight # finish this page canvas.showPage() return canvas
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)
def create_pdf(hocr, filename, font="Courier", author=None, keywords=None, subject=None, title=None, image_path=None, draft=False): """ transform hOCR information into a searchable PDF. @param hocr the hocr structure as coming from extract_hocr. @param filename the name of the PDF generated in output. @param font the default font (e.g. Courier, Times-Roman). @param author the author name. @param subject the subject of the document. @param title the title of the document. @param image_path the default path where images are stored. If not specified relative image paths will be resolved to the current directory. @param draft whether to enable debug information in the output. """ def adjust_image_size(width, height): return max(width / A4[0], height / A4[1]) canvas = Canvas(filename) if author: canvas.setAuthor(author) if keywords: canvas.setKeywords(keywords) if title: canvas.setTitle(title) if subject: canvas.setSubject(subject) for bbox, image, lines in hocr: if not image.startswith('/') and image_path: image = os.path.abspath(os.path.join(image_path, image)) img_width, img_height = bbox[2:] ratio = adjust_image_size(img_width, img_height) if draft: canvas.drawImage(image, 0, A4[1] - img_height / ratio , img_width / ratio, img_height / ratio) canvas.setFont(font, 12) for bbox, line in lines: if draft: canvas.setFillColor(red) x0, y0, x1, y1 = bbox width = (x1 - x0) / ratio height = ((y1 - y0) / ratio) x0 = x0 / ratio #for ch in 'gjpqy,(){}[];$@': #if ch in line: #y0 = A4[1] - (y0 / ratio) - height #break #else: y0 = A4[1] - (y0 / ratio) - height / 1.3 #canvas.setFontSize(height * 1.5) canvas.setFontSize(height) text_width = canvas.stringWidth(line) if text_width: ## If text_width != 0 text_object = canvas.beginText(x0, y0) text_object.setHorizScale(1.0 * width / text_width * 100) text_object.textOut(line) canvas.drawText(text_object) else: info('%s, %s has width 0' % (bbox, line)) if draft: canvas.setStrokeColor(green) canvas.rect(x0, y0, width, height) if draft: canvas.circle(0, 0, 10, fill=1) canvas.circle(0, A4[1], 10, fill=1) canvas.circle(A4[0], 0, 10, fill=1) canvas.circle(A4[0], A4[1], 10, fill=1) canvas.setFillColor(green) canvas.setStrokeColor(green) canvas.circle(0, A4[1] - img_height / ratio, 5, fill=1) canvas.circle(img_width / ratio, img_height /ratio, 5, fill=1) else: canvas.drawImage(image, 0, A4[1] - img_height / ratio , img_width / ratio, img_height / ratio) canvas.save()
def create_pdf(hocr, filename, font="Courier", author=None, keywords=None, subject=None, title=None, image_path=None, draft=False): """ transform hOCR information into a searchable PDF. @param hocr the hocr structure as coming from extract_hocr. @param filename the name of the PDF generated in output. @param font the default font (e.g. Courier, Times-Roman). @param author the author name. @param subject the subject of the document. @param title the title of the document. @param image_path the default path where images are stored. If not specified relative image paths will be resolved to the current directory. @param draft whether to enable debug information in the output. """ def adjust_image_size(width, height): return max(width / A4[0], height / A4[1]) canvas = Canvas(filename) if author: canvas.setAuthor(author) if keywords: canvas.setKeywords(keywords) if title: canvas.setTitle(title) if subject: canvas.setSubject(subject) for bbox, image, lines in hocr: if not image.startswith('/') and image_path: image = os.path.abspath(os.path.join(image_path, image)) img_width, img_height = bbox[2:] ratio = adjust_image_size(img_width, img_height) if draft: canvas.drawImage(image, 0, A4[1] - img_height / ratio, img_width / ratio, img_height / ratio) canvas.setFont(font, 12) for bbox, line in lines: if draft: canvas.setFillColor(red) x0, y0, x1, y1 = bbox width = (x1 - x0) / ratio height = ((y1 - y0) / ratio) x0 = x0 / ratio #for ch in 'gjpqy,(){}[];$@': #if ch in line: #y0 = A4[1] - (y0 / ratio) - height #break #else: y0 = A4[1] - (y0 / ratio) - height / 1.3 #canvas.setFontSize(height * 1.5) canvas.setFontSize(height) text_width = canvas.stringWidth(line) if text_width: ## If text_width != 0 text_object = canvas.beginText(x0, y0) text_object.setHorizScale(1.0 * width / text_width * 100) text_object.textOut(line) canvas.drawText(text_object) else: info('%s, %s has width 0' % (bbox, line)) if draft: canvas.setStrokeColor(green) canvas.rect(x0, y0, width, height) if draft: canvas.circle(0, 0, 10, fill=1) canvas.circle(0, A4[1], 10, fill=1) canvas.circle(A4[0], 0, 10, fill=1) canvas.circle(A4[0], A4[1], 10, fill=1) canvas.setFillColor(green) canvas.setStrokeColor(green) canvas.circle(0, A4[1] - img_height / ratio, 5, fill=1) canvas.circle(img_width / ratio, img_height / ratio, 5, fill=1) else: canvas.drawImage(image, 0, A4[1] - img_height / ratio, img_width / ratio, img_height / ratio) canvas.save()
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()