from reportlab.pdfbase.pdfmetrics import (getAscent, getDescent) from reportlab.lib.units import inch from reportlab.pdfgen import canvas import datetime from reportlab.platypus.doctemplate import BaseDocTemplate, PageTemplate from reportlab.platypus.frames import Frame from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont from ptulsconv.docparser.adr_entity import ADRLine # This is from https://code.activestate.com/recipes/576832/ for # generating page count messages class ReportCanvas(canvas.Canvas): def __init__(self, *args, **kwargs): canvas.Canvas.__init__(self, *args, **kwargs) self._saved_page_states = [] self._report_date = datetime.datetime.now() def showPage(self): self._saved_page_states.append(dict(self.__dict__)) self._startPage() def save(self): """add page info to each page (page x of y)""" num_pages = len(self._saved_page_states) for state in self._saved_page_states: self.__dict__.update(state) self.draw_page_number(num_pages) canvas.Canvas.showPage(self) canvas.Canvas.save(self) def draw_page_number(self, page_count): self.saveState() self.setFont("Futura", 10) self.drawString(0.5 * inch, 0.5 * inch, "Page %d of %d" % (self._pageNumber, page_count)) right_edge = self._pagesize[0] - 0.5 * inch self.drawRightString(right_edge, 0.5 * inch, self._report_date.strftime("%m/%d/%Y %H:%M")) top_line = self.beginPath() top_line.moveTo(0.5 * inch, 0.75 * inch) top_line.lineTo(right_edge, 0.75 * inch) self.setLineWidth(0.5) self.drawPath(top_line) self.restoreState() class ADRDocTemplate(BaseDocTemplate): def build(self, flowables, filename=None, canvasmaker=ReportCanvas): BaseDocTemplate.build(self, flowables, filename, canvasmaker) def make_doc_template(page_size, filename, document_title, record: ADRLine, document_header="", left_margin=0.5 * inch): right_margin = top_margin = bottom_margin = 0.5 * inch page_box = GRect(0., 0., page_size[0], page_size[1]) _, page_box = page_box.split_x(left_margin, direction='l') _, page_box = page_box.split_x(right_margin, direction='r') _, page_box = page_box.split_y(bottom_margin, direction='u') _, page_box = page_box.split_y(top_margin, direction='d') footer_box, page_box = page_box.split_y(0.25 * inch, direction='u') header_box, page_box = page_box.split_y(0.75 * inch, direction='d') title_box, report_box = header_box.split_x(3.5 * inch, direction='r') page_template = PageTemplate(id="Main", frames=[Frame(page_box.min_x, page_box.min_y, page_box.width, page_box.height)], onPage=lambda c, _: draw_header_footer(c, title_box, report_box, footer_box, record, doc_title=document_header)) pdfmetrics.registerFont(TTFont('Futura', 'Futura.ttc')) doc = ADRDocTemplate(filename, title=document_title, author=record.supervisor, pagesize=page_size, leftMargin=left_margin, rightMargin=right_margin, topMargin=top_margin, bottomMargin=bottom_margin) doc.addPageTemplates([page_template]) return doc def time_format(mins, zero_str=""): if mins == 0. and zero_str is not None: return zero_str elif mins < 60.: return "%im" % round(mins) else: m = round(mins) hh, mm = divmod(m, 60) return "%i:%02i" % (hh, mm) def draw_header_footer(a_canvas: ReportCanvas, title_box, doc_title_box, footer_box, record: ADRLine, doc_title=""): (supervisor, client,), title = title_box.divide_y([16., 16., ]) title.draw_text_cell(a_canvas, record.title, "Futura", 18, inset_y=2., inset_x=5.) client.draw_text_cell(a_canvas, record.client, "Futura", 11, inset_y=2., inset_x=5.) a_canvas.saveState() a_canvas.setLineWidth(0.5) tline = a_canvas.beginPath() tline.moveTo(doc_title_box.min_x, title_box.min_y) tline.lineTo(title_box.max_x, title_box.min_y) a_canvas.drawPath(tline) tline2 = a_canvas.beginPath() tline2.moveTo(title_box.min_x, title_box.min_y) tline2.lineTo(title_box.min_x, title_box.max_y) a_canvas.drawPath(tline2) a_canvas.restoreState() (doc_title_cell, spotting_version_cell,), _ = doc_title_box.divide_y([18., 14], direction='d') doc_title_cell.draw_text_cell(a_canvas, doc_title, 'Futura', 14., inset_y=2.) if record.spot is not None: spotting_version_cell.draw_text_cell(a_canvas, record.spot, 'Futura', 12., inset_y=2.) a_canvas.setFont('Futura', 11.) a_canvas.drawCentredString(footer_box.min_x + footer_box.width / 2., footer_box.min_y, record.supervisor or "") class GRect: def __init__(self, x, y, width, height, debug_name=None): self.x = x self.y = y self.width = width self.height = height self.debug_name = debug_name self.normalize() @property def min_x(self): return self.x @property def min_y(self): return self.y @property def max_x(self): return self.x + self.width @property def max_y(self): return self.y + self.height @property def center_x(self): return self.x + self.width / 2 @property def center_y(self): return self.y + self.height / 2 def normalize(self): if self.width < 0.: self.width = abs(self.width) self.x = self.x - self.width if self.height < 0.: self.height = abs(self.height) self.y = self.y - self.height def split_x(self, at, direction='l'): if at >= self.width: return None, self elif at <= 0: return self, None else: if direction == 'l': return (GRect(self.min_x, self.min_y, at, self.height), GRect(self.min_x + at, self.y, self.width - at, self.height)) else: return (GRect(self.max_x - at, self.y, at, self.height), GRect(self.min_x, self.y, self.width - at, self.height)) def split_y(self, at, direction='u'): if at >= self.height: return None, self elif at <= 0: return self, None else: if direction == 'u': return (GRect(self.x, self.y, self.width, at), GRect(self.x, self.y + at, self.width, self.height - at)) else: return (GRect(self.x, self.max_y - at, self.width, at), GRect(self.x, self.y, self.width, self.height - at)) def inset_xy(self, dx, dy): return GRect(self.x + dx, self.y + dy, self.width - dx * 2, self.height - dy * 2) def inset(self, d): return self.inset_xy(d, d) def __repr__(self): return "" % (self.x, self.y, self.width, self.height) def divide_x(self, x_list, direction='l'): ret_list = list() rem = self for item in x_list: s, rem = rem.split_x(item, direction) ret_list.append(s) return ret_list, rem def divide_y(self, y_list, direction='u'): ret_list = list() rem = self for item in y_list: s, rem = rem.split_y(item, direction) ret_list.append(s) return ret_list, rem def draw_debug(self, a_canvas): a_canvas.saveState() a_canvas.setFont("Courier", 8) a_canvas.rect(self.x, self.y, self.width, self.height) a_canvas.drawString(self.x, self.y, self.debug_name or self.__repr__()) a_canvas.restoreState() def draw_border(self, a_canvas, edge): def draw_border_impl(en): if en == 'min_x': coordinates = ((self.min_x, self.min_y), (self.min_x, self.max_y)) elif en == 'max_x': coordinates = ((self.max_x, self.min_y), (self.max_x, self.max_y)) elif en == 'min_y': coordinates = ((self.min_x, self.min_y), (self.max_x, self.min_y)) elif en == 'max_y': coordinates = ((self.min_x, self.max_y), (self.max_x, self.max_y)) else: return s = a_canvas.beginPath() s.moveTo(*coordinates[0]) s.lineTo(*coordinates[1]) a_canvas.drawPath(s) if type(edge) is str: edge = [edge] for e in edge: draw_border_impl(e) def draw_text_cell(self, a_canvas, text, font_name, font_size, vertical_align='t', force_baseline=None, inset_x=0., inset_y=0., draw_baseline=False): if text is None: return a_canvas.saveState() inset_rect = self.inset_xy(inset_x, inset_y) if vertical_align == 'm': y = inset_rect.center_y - getAscent(font_name, font_size) / 2. elif vertical_align == 't': y = inset_rect.max_y - getAscent(font_name, font_size) else: y = inset_rect.min_y - getDescent(font_name, font_size) if force_baseline is not None: y = self.min_y + force_baseline cp = a_canvas.beginPath() cp.rect(self.min_x, self.min_y, self.width, self.height) a_canvas.clipPath(cp, stroke=0, fill=0) a_canvas.setFont(font_name, font_size) tx = a_canvas.beginText() tx.setTextOrigin(inset_rect.min_x, y) tx.textLine(text) a_canvas.drawText(tx) if draw_baseline: a_canvas.setDash([3.0, 1.0, 2.0, 1.0]) a_canvas.setLineWidth(0.5) bl = a_canvas.beginPath() bl.moveTo(inset_rect.min_x, y - 1.) bl.lineTo(inset_rect.max_x, y - 1.) a_canvas.drawPath(bl) a_canvas.restoreState() def draw_flowable(self, a_canvas, flowable, inset_x=0., inset_y=0., draw_baselines=False): a_canvas.saveState() inset_rect = self.inset_xy(inset_x, inset_y) cp = a_canvas.beginPath() cp.rect(self.min_x, self.min_y, self.width, self.height) a_canvas.clipPath(cp, stroke=0, fill=0) w, h = flowable.wrap(inset_rect.width, inset_rect.height) flowable.drawOn(a_canvas, inset_rect.x, inset_rect.max_y - h) if draw_baselines: a_canvas.setDash([3.0, 1.0, 2.0, 1.0]) a_canvas.setLineWidth(0.5) leading = flowable.style.leading y = inset_rect.max_y - flowable.style.fontSize - 1. while y > inset_rect.min_x: bl = a_canvas.beginPath() bl.moveTo(inset_rect.min_x, y) bl.lineTo(inset_rect.max_x, y) a_canvas.drawPath(bl) y = y - leading a_canvas.restoreState()