def test_link_with_zoom_and_shift(tmp_path): pdf = FPDF() pdf.set_font("helvetica", size=24) pdf.add_page() link = pdf.add_link() pdf.set_link(link, page=2, x=pdf.epw / 4, y=pdf.epw / 3, zoom=4) pdf.set_xy(30, 50) pdf.cell( w=140, h=10, txt="Link to 2nd page zoomed & shifted", border=1, align="C", link=link, ) pdf.add_page() pdf.multi_cell( pdf.epw, txt="Lorem ipsum dolor sit amet, consectetur adipiscing elit," " sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." " Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." " Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur." " Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", ) # Drawing Adobe Reader viewport after clicking on the link, # with the right panel open. The initial zoom level does not matter. pdf.set_draw_color(r=255, g=0, b=0) pdf.rect(x=pdf.epw / 4, y=pdf.epw / 3, w=53.5, h=31) assert_pdf_equal(pdf, HERE / "link_with_zoom_and_shift.pdf", tmp_path)
def write_to_pdf(data: dict, filename: str): pdf = FPDF() effective_page_width = pdf.w - 2 * pdf.l_margin pdf.compress = False pdf.add_page() pdf.add_font("TimesNewRoman", '', 'TimesNewRoman.ttf', uni=True) pdf.set_font("TimesNewRoman", size=30) pdf.cell(w=0, txt=data['title']) pdf.ln(30) pdf.set_line_width(1) pdf.set_draw_color(255, 0, 0) for index_news, news_dict in enumerate(data['items']): pdf.set_font("TimesNewRoman", size=20) pdf.line(20, pdf.get_y() - 10, effective_page_width, pdf.get_y() - 10) pdf.multi_cell(effective_page_width, 10, news_dict['title']) if news_dict['contain_image']: download_image_and_paste_in_pdf(pdf, news_dict, index_news) pdf.multi_cell(effective_page_width, 10, news_dict['published']) pdf.multi_cell(effective_page_width, 10, news_dict['summary'][news_dict['summary'].rfind(']') + 1:]) pdf.set_font("TimesNewRoman", size=15) pdf.ln(5) pdf.multi_cell(effective_page_width, 10, 'Link on news:\n' + news_dict['link']) if news_dict['contain_image']: pdf.multi_cell(effective_page_width, 10, 'Link on image:\n' + news_dict['link_on_image']) pdf.ln(40) try: pdf.output(filename, 'F') except PermissionError: raise RssReaderException.FileException(f'close file:\n{filename}')
class PDFGridGenerator(): """A pdf grid generator. To use: >>> g = PDFGridGenerator() >>> g.generate('filename.pdf') """ def __init__(self): self.pdf = FPDF() self.pdf.add_page() self.pdf.set_auto_page_break(False) self.pdf.set_margins(0, 0) self.pdf.set_draw_color(191, 187, 187) self.pdf.set_line_width(0.35) def save(self, filename): self.pdf.output(filename) def generate_grid(self): # Fonts must be set before writing text self.pdf.set_font('Arial', 'B', 8) self.pdf.set_text_color(0, 0, 0) self.generate_vertical_lines(5) self.generate_horizontal_lines(5) self.left_numbers(5) self.top_numbers(5) def generate_vertical_lines(self, spacing): lines = 42 for i in range(1, lines): x = i * 5 self.pdf.line(x, 5, x, 290) def generate_horizontal_lines(self, spacing): lines = 59 for j in range(1, lines): y = j * 5 self.pdf.line(5, y, 205, y) def left_numbers(self, spacing): lines = 58 for i in range(0, lines): self.pdf.set_xy(0, i * 5 + 3) self.pdf.write(5, str(i)) def top_numbers(self, spacing): lines = 41 for i in range(0, lines): self.pdf.set_xy(i * 5 + 3, 1) self.pdf.write(5, str(i)) def text(self, x, y, txt, font_size=13): self.pdf.set_font('Arial', '', font_size) self.pdf.set_text_color(0, 0, 0) self.pdf.set_xy(x * 5 + 4, y * 5 + 5) self.pdf.write(5, txt)
def create_publication_cert(uuid, name, author, journal, publication_date, created_date): pdf = FPDF() pdf.add_page() pdf.set_margins(25, 15, 15) pdf.image(settings.STATIC_ROOT + "/pdf/logo.png", 25, 15, 50) pdf.add_font('Arial', '', settings.STATIC_ROOT + '/pdf/arial.ttf', uni=True) pdf.set_font('Arial', '', 8) pdf.set_draw_color(50, 50, 50) pdf.cell(90, 5, ln=1) pdf.cell(90) pdf.cell(0, 3, "АНС «СибАК» ИНН 5404255395 КПП 540201001", ln=1) pdf.cell(90) pdf.cell(0, 3, "Адрес: г.Новосибирск, ул. Красный проспект, 165, офис 4", ln=1) pdf.cell(90) pdf.cell(0, 3, "Телефон: 8-913-915-38-00", ln=1) pdf.cell(90) pdf.cell(0, 3, "Р/с 40703810029100000978 в ОАО АКБ \"АВАНГАРД\"", ln=1) pdf.cell(90) pdf.cell(0, 3, "к/с 3010181000000000201", ln=1) pdf.cell(90) pdf.cell(0, 3, "БИК 044525201", ln=1) pdf.set_font_size(16) pdf.cell(0, 30, "СПРАВКА", ln=1, align="C") pdf.set_font_size(12) pdf.cell(0, 6, created_date.strftime('%d.%m.%Y') + " No. 14362", ln=1, align="L") pdf.cell(0, 6, ln=1) pdf.cell(0, 6, author, ln=1, align="R") pdf.cell(0, 6, ln=1) spisok = [name, journal, publication_date.strftime('%d.%m.%Y')] pdf.multi_cell( 0, 6, "Издательство подтверждает, что Ваша статья «{}» \ принята для публикации в научном журнале «{}». \ Журнал будет опубликован на сайте издательства {}.".format(*spisok)) pdf.cell(0, 24, ln=1) pathlib.Path(settings.MEDIA_ROOT + '/pdf/publication_cert').mkdir( parents=True, exist_ok=True) pdf.output( settings.MEDIA_ROOT + "/pdf/publication_cert/publication_cert_" + str(uuid) + ".pdf", 'F') return "/pdf/publication_cert/publication_cert_" + str(uuid) + ".pdf"
def draw_lines(): pdf = FPDF() pdf.add_page() pdf.line(10, 10, 10, 100) pdf.set_line_width(1) pdf.set_draw_color(255, 0, 0) pdf.line(20, 20, 100, 20) pdf.output('draw_lines.pdf')
def draw_lines(request): pdf = FPDF() pdf.add_page() pdf.line(10, 10, 10, 100) pdf.set_line_width(1) pdf.set_draw_color(255, 0, 0) pdf.line(20, 20, 100, 20) pdf.output('draw_lines.pdf') return render(request, "index.html")
class Report: def __init__(self, data): """Initialize the Report object.""" self.pdf = FPDF("P", "mm", "A4") self.id = data["id"] self.data = data def render(self): self.pdf.add_page() self.draw_shapes() self.draw_title() self.draw_heading() self.draw_inventory() return self.pdf.output("report_%s.pdf" % self.data["id"]) def draw_shapes(self): """Draw the report edge.""" self.pdf.set_line_width(4) self.pdf.rect(10, 10, 190, 277) self.pdf.set_line_width(2) self.pdf.rect(15, 15, 180, 267) self.pdf.set_draw_color(192, 192, 192) self.pdf.set_line_width(1) self.pdf.rect(16, 16, 178, 265) def draw_inventory(self): """Draw the inventory paragraph the report.""" self.pdf.set_y(100) self.pdf.set_font("Arial", "", 18) self.pdf.cell(170, 7, "Inventory", 0, ln=2, align="C") self.pdf.set_font("Arial", "", 12) for inventory in self.data["inventory"]: text = "%s: %s" % (inventory["name"], inventory["price"]) self.pdf.cell(150, 7, text, 0, ln=2, align="C") def draw_title(self): """Draw the report title.""" title = self.data["organization"] + " report" self.pdf.set_font("Arial", "B", 20) w = self.pdf.get_string_width(title) x = 210 - w / 2 self.pdf.cell(x, 50, title, 0, 1, "C") def draw_heading(self): """Draw the report heading.""" self.pdf.set_font("Arial", "", 12) self.pdf.set_x(0) org_text = "Organization: %s " % self.data["organization"] self.pdf.cell(170, 7, org_text, 0, ln=2, align="R") reported_text = "Reported: %s " % self.data["reported_at"] self.pdf.cell(170, 7, reported_text, 0, ln=2, align="R") created_text = "Created: %s " % self.data["created_at"] self.pdf.cell(170, 7, created_text, 0, ln=2, align="R")
def test_local_context_inherited_shared_props(tmp_path): "The only thing that should differ between the 2 squares is their opacity" pdf = FPDF() pdf.add_page() pdf.set_font("Helvetica", "", 12) pdf.set_draw_color(0, 128, 255) pdf.set_fill_color(255, 128, 0) pdf.set_line_width(2) pdf.set_dash_pattern(dash=0.5, gap=9.5, phase=3.25) with pdf.local_context( fill_opacity=0.5): # => triggers creation of a local GraphicsStyle pdf.rect(x=60, y=60, w=60, h=60, style="DF") pdf.rect(x=60, y=150, w=60, h=60, style="DF") assert_pdf_equal(pdf, HERE / "local_context_inherited_shared_props.pdf", tmp_path)
def printReport(): df = pd.read_csv("OUTPUT/HDIrank2019_02_mergedDataframes.csv") df = df.drop(columns="Unnamed: 0") reg = df.groupby("region").mean().drop( columns=["HDIrank", "LastYearRank"]).T.round(2) pdf = FPDF('P', 'mm', 'A4') pdf.add_page() font_type = ('Arial', 'B', 16) num_col = 6 w, h = 190, 277 pdf.set_font(*font_type) pdf.set_text_color(0) pdf.set_draw_color(0) pdf.cell(w, 10, 'Datos Acumulados', 1, 1, 'C') pdf.set_line_width(0.2) for col in reg.columns: pdf.cell(w / num_col, 10, col, 1, 0, 'C') pdf.ln() pdf.set_fill_color(243, 95, 95) font_type = ('Arial', '', 12) pdf.set_font(*font_type) ''' NOT WORKING! def fit_word(string,cell_w,font_type): ver = FPDF() #font_type(font,style,size)) ver.set_font(*font_type) # if string fits, return it unchanged if ver.get_string_width(string)<cell_w: return string # cut string until it fits while ver.get_string_width(string)>=cell_w: string = string[:-1] # replace last 3 characters with "..." string = string[:-3] + "..." return string ''' for index, row in reg.iterrows(): for value in reg.columns: pdf.cell(w / num_col, 10, row[value], 1, 0, 'C', 0) pdf.ln() pdf.output("OUTPUT/report.pdf", 'F') return "Repo done"
def gen_PDF(events): pdf = FPDF('L', 'in', 'Letter') for event in events: pdf.add_page() pdf.set_draw_color(r=0, g=0, b=255) pdf.set_line_width(.05) pdf.rect(.15, .15, 10.7, 8.2) pdf.image('images/3IDcav3.png', x=.14, y=.22, h=1) pdf.image('images/3IDLogo.png', x=9.8, y=.22, h=1) pdf.set_draw_color(r=0, g=0, b=0) pdf.line(5.5, 1.5, 5.5, 8) pdf.line(.5, 4.25, 10.5, 4.25) pdf.set_font('Arial', 'B', 24) pdf.set_xy(0, .75) pdf.cell(w=0, txt=events[event].title, align='C') pdf.set_xy(1, 1.5) pdf.set_font('Arial', '', 14) pdf.multi_cell( 4.5, .22, 'Purpose: {}\n\nFrequency: {}\n\nTime: {}\n\nLocation: {}'.format( events[event].purpose, events[event].frequency, events[event].time, events[event].location), 0, 'L') pdf.set_xy(1, 4.5) pdf.multi_cell( 4.5, .22, 'Inputs: \n{}\nOutputs: \n{}'.format( ' - ' + str(events[event].inputs)[1:-1].replace( "'", "").replace(',', '\n -'), ' - ' + str(events[event].outputs)[1:-1].replace( "'", "").replace(',', '\n -'), 0, 'L')) pdf.set_xy(6.5, 1.5) pdf.multi_cell( 4.5, .22, 'Chair: {}\n\nMembers: \n{}'.format( events[event].chair, ' - ' + str(events[event].members)[1:-1].replace( "'", "").replace(',', '\n -'), 0, 'L')) pdf.set_xy(6.5, 4.5) pdf.multi_cell( 4.5, .22, 'Agenda: \n{}'.format( ' - ' + str(events[event].agenda)[1:-1].replace( "'", "").replace(',', '\n -'), 0, 'L')) try: pdf.output('exports/test.pdf') except FileNotFoundError: os.mkdir('exports') pdf.output('exports/test.pdf')
def test_text_modes(tmp_path): pdf = FPDF(format=(350, 150)) pdf.add_page() pdf.set_font("Helvetica", size=80) with pdf.local_context(fill_color=(255, 128, 0)): pdf.cell(txt="FILL default") with pdf.local_context(text_color=(0, 128, 255)): pdf.cell(txt=" text mode") pdf.ln() with pdf.local_context(text_mode=TextMode.STROKE, line_width=2): pdf.cell(txt="STROKE text mode") pdf.ln() pdf.text_mode = TextMode.FILL_STROKE pdf.line_width = 4 pdf.set_draw_color(255, 0, 255) pdf.cell(txt="FILL_STROKE text mode") pdf.ln() with pdf.local_context(): pdf.text_mode = TextMode.INVISIBLE pdf.cell(txt="INVISIBLE text mode") assert_pdf_equal(pdf, HERE / "text_modes.pdf", tmp_path)
def output_pdf(logger, all_news, about_website=None, file_name=None): """Function which create or overwrites PDF-file with selected fresh or cached news""" logger.info('Convert to PDF-format') pdf = FPDF() pdf.add_page() if about_website is not None: pdf.set_font("Arial", "B", size=14) pdf.set_fill_color(200, 220, 255) for value in about_website.values(): line = 1 pdf.cell(190, 8, txt=value, ln=line, align="C") line += 1 pdf.set_font("Arial", size=10) pdf.set_line_width(1) pdf.set_draw_color(35, 41, 153) for news in all_news: link = news['Source of image'] for key, value in news.items(): if key != 'Summary': pdf.multi_cell(190, 6, txt=f'{key}: {value}', align="L") else: position_y = pdf.get_y() try: filename, _ = urllib.request.urlretrieve(link) pdf.image(filename, 80, position_y, h=30, type='jpeg', link=link) pdf.ln(31) os.remove(filename) except Exception as ex: logger.warning("Error finding image: {}, {}.".format(type(ex), ex)) pdf.multi_cell(190, 6, txt=f'{key}: {value}', align="L") position_y = pdf.get_y() pdf.set_line_width(1) pdf.set_draw_color(35, 41, 153) pdf.line(10, position_y, 200, position_y) logger.info('Creating of PDF-file') try: pdf.output(file_name) logger.info('Converted successfully!') except Exception as ex: logger.error("PDF file writing error : {}, {}.".format(type(ex), ex))
def make_overpdf(overfile, info, pagenum, pagecount): # Assessor name top left of header # processor name and version left of footer # Date and Page numbers in right footer # Initialize the pdf pdf = FPDF(orientation="P", unit='in', format='letter') pdf.set_margins(left=0, top=0, right=0) pdf.add_page() pdf.set_font('courier', size=9) # Draw lines in header and footer pdf.set_draw_color(r=155, g=155, b=155) pdf.set_line_width(0.01) pdf.line(x1=0.2, y1=0.2, x2=8.3, y2=0.2) pdf.line(x1=0.2, y1=10.7, x2=8.3, y2=10.7) # Write the assessor label in the header pdf.set_xy(0.3, 0.1) pdf.cell(0, 0.05, '{}'.format(info['assessor'])) # Prevent footer from falling off page pdf.set_auto_page_break(False) # Write proc version in left of footer pdf.set_xy(0.3, -0.2) pdf.cell(0, 0, '{}'.format(info['proctype'])) # Write date in right of footer pdf.set_xy(-2.1, -0.2) pdf.cell(0, 0, '{}'.format(info['procdate'])) # Write page numbers right of footer pdf.set_xy(-0.6, -0.2) pdf.cell(0, 0, '{}/{}'.format(pagenum, pagecount)) # Write the pdf to file pdf.output(overfile)
def main(): pdf = FPDF() pdf.add_page() pdf.set_xy(0, 0) pdf.set_font('arial', 'B', 13.0) pdf.image("image_1.jpg", 75, 30) setCandidateName() setColorsChart() readMetrics() setMetricsWidths() setDiscsWidths() setLine(pdf, 45, 75, 70, 0, metrics[0]) setLine(pdf, 40, 70, 90, 1, metrics[1]) setLine(pdf, 45, 75, 110, 2, metrics[2]) setLine(pdf, 118, 148, 85, 3, metrics[3]) setLine(pdf, 135, 165, 65, 4, metrics[4]) setLine(pdf, 122, 152, 104, 5, metrics[5]) setLine(pdf, 115, 145, 120, 6, metrics[6]) pdf.set_font('arial', 'B', 13.0) #addCandidateName pdf.set_draw_color(0,0,0) pdf.set_y(140) pdf.cell(70) pdf.cell(w = 60, h = 10, ln =1, txt = candidate, border = 'B', align = 'C') #addDiscTestResult pdf.image("DISC.jpg", 65, 180, 80,80) setLine(pdf,45,65,200,0,discs[0]) setLine(pdf,45,65,245,5,discs[1]) setLine(pdf,145,165,200,8,discs[2]) setLine(pdf,145,165,240,2,discs[3]) #generateReportFile pdf.output('candidateSurvey.pdf', 'F')
def some_initialisations(self, copyname): document = FPDF(orientation='P', unit='mm', format='A4') doctitle = "Tax invoice - " + copyname document.set_title(doctitle) document.set_margins( 20, 20, 20 ) # left, top, right...... Define before creating page, otherwise will ruin formatting #Libreoffice ref: 11pt=3.88mm, 48 lines @1.2, 50 lines @1.15, 38 lines @1.5 line spacing # big character to font height is 75% approx. #space to char ht: 89%, 99%, 75%, 153% respectively for 1.15, 1.2, 1 and 1.5 line spacing #net char height for 11pt is 3.88*.75=2.91 mm #net space height for 11pt is 2.59 @1.15, 2.88 @1.2, 2.18 @1, 4.45 @1.5 # cell ht: 5.5 @1.15, 5.8 @1.2, 5.1 @1, 7.35 @1.5 #There is no page for the moment, so we have to add one with add_page. document.add_page() document.set_font("Times", size=11) document.set_text_color(20, 20, 20) document.set_fill_color(255, 255, 255) # cell fill colour, visible if cell property fill set to true document.set_draw_color(175, 175, 175) # border color, used for all drawing operations (lines, rectangles and cell borders) return document
class kut2fpdf(object): _document = None # Aquí se irán guardando los datos del documento logger = None _xml = None _xml_data = None _page_orientation = None _page_size = None _bottom_margin = None _left_margin = None _right_margin = None _top_margin = None _page_top = {} _data_row = None # Apunta a la fila actual en data _parser_tools = None _avalible_fonts = None _unavalible_fonts = None design_mode = None _actual_data_line = None _no_print_footer = False _actual_section_size = None increase_section_size = None last_detail = False actual_data_level = None last_data_processed = None prev_level = None draws_at_header = None detailn = None name_ = None _actual_append_page_no = None reset_page_count = None def __init__(self): self.logger = logging.getLogger("kut2fpdf") checkDependencies({"fpdf": "pyfpdf"}) from pineboolib.plugins.kugar.parsertools import parsertools self._parser_tools = parsertools() self._avalible_fonts = [] self._unavalible_fonts = [] self.design_mode = FLSettings().readBoolEntry("ebcomportamiento/kugar_debug_mode") self._actual_data_line = None self._no_print_footer = False self.increase_section_size = 0 self.actual_data_level = 0 self.prev_level = -1 self.draws_at_header = {} self.detailn = {} self.name_ = None self._actual_append_page_no = 0 self.reset_page_count = False self.new_page = False """ Convierte una cadena de texto que contiene el ".kut" en un pdf y retorna la ruta a este último. @param name. Nombre de ".kut". @param kut. Cadena de texto que contiene el ".kut". @param data. Cadena de texto que contiene los datos para ser rellenados en el informe. @return Ruta a fichero pdf. """ def parse(self, name, kut, data, report = None, flags = []): try: self._xml = self._parser_tools.loadKut(kut) except Exception: self.logger.exception( "KUT2FPDF: Problema al procesar %s.kut", name) return False try: self._xml_data = load2xml(data) except Exception: self.logger.exception("KUT2FPDF: Problema al procesar xml_data") return False self.name_ = name self.setPageFormat(self._xml) # self._page_orientation = # self._page_size = if report is None: from fpdf import FPDF self._actual_append_page_no = -1 self._document = FPDF(self._page_orientation, "pt", self._page_size) for f in self._document.core_fonts: self.logger.debug("KUT2FPDF :: Adding font %s", f) self._avalible_fonts.append(f) else: self._document = report # Seteamos rutas a carpetas con tipos de letra ... # Cargamos las fuentes disponibles next_page_break = (flags[2] == 1) if len(flags) == 3 else True page_append = (flags[1] == 1) if len(flags) > 1 else False page_display = (flags[0] == 1) if len(flags) > 0 else False if page_append: self.prev_level = -1 self.last_detail = False page_break = False if self.new_page: page_break = True self.new_page = False if self.reset_page_count: self.reset_page_no() self.reset_page_count = False if self.design_mode: print("Append", page_append) print("Display", page_display) print("Page break", next_page_break) if next_page_break: self.reset_page_count = True if page_display: self.new_page = True self.processDetails(not page_break) #FIXME:Alguno valores no se encuentran for p in self._document.pages.keys(): page_content = self._document.pages[p]["content"] for h in self.draws_at_header.keys(): page_content = page_content.replace(h, str(self.draws_at_header[h])) self._document.pages[p]["content"] = page_content #print(self.draws_at_header.keys()) self._document.set_title(self.name_) self._document.set_author("Pineboo - kut2fpdf plugin") return self._document def get_file_name(self): import os pdf_name = aqApp.tmp_dir() pdf_name += "/%s_%s.pdf" % (self.name_, datetime.datetime.now().strftime("%Y%m%d%H%M%S")) if os.path.exists(pdf_name): os.remove(pdf_name) if self._document is not None: self._document.output(pdf_name, 'F') return pdf_name else: return None """ Indica el techo para calcular la posición de los objetos de esa sección. @return Número con el techo de la página actual. """ def topSection(self): return self._page_top[str(self._document.page_no())] """ Actualiza el valor del techo de la página actual. Se suele actualizar al procesar una sección. @param value. Numero que elspecifica el nuevo techo. """ def setTopSection(self, value): self._actual_section_size = value - self.topSection() self._page_top[str(self._document.page_no())] = value """ Añade una nueva página al documento. """ def newPage(self, data_level, add_on_header = True): self._document.add_page(self._page_orientation) self._page_top[str(self._document.page_no())] = self._top_margin self._document.set_margins(self._left_margin, self._top_margin, self._right_margin) # Lo dejo pero no se nota nada self._no_print_footer = False if self.design_mode: self.draw_margins() self._actual_section_size = 0 self._actual_append_page_no += 1 if self.design_mode: print("Nueva página", self.number_pages()) #l_ini = data_level #l_end = self.prev_level #if l_ini == l_end: # l_end = l_end + 1 #if l_ini <= l_end: # for l in range(l_ini , l_end): # print(l) # self.processSection("AddOnHeader", str(l)) pg_headers = self._xml.findall("PageHeader") for ph in pg_headers: if self.number_pages() == 0 or ph.get("PrintFrequency") == "1": ph_level = ph.get("Level") if ph.get("Level") is not None else None self.processSection("PageHeader", ph_level) break if add_on_header and not self.number_pages() == 0: for l in range(data_level + 1): self.processSection("AddOnHeader", str(l)) #Por ahora se omite detail header """ Procesa las secciones details con sus correspondientes detailHeader y detailFooter. """ def processDetails(self, keep_page = None): # Procesamos la cabecera si procede .. top_level = 0 level = 0 first_page_created = keep_page if keep_page is not None and self._document.page_no() > 0 else False rows_array = self._xml_data.findall("Row") for data in rows_array: self._actual_data_line = data level = int(data.get("level")) if level > top_level: top_level = level if not first_page_created: self.newPage(level) first_page_created = True if rows_array[len(rows_array) - 1] is data: self.last_detail = True if level < self.prev_level: for l in range(level + 1, self.prev_level + 1): self.processData("DetailFooter", self.last_data_processed, l) if not str(level) in self.detailn.keys(): self.detailn[str(level)] = 0 else: self.detailn[str(level)] += 1 if level > self.prev_level: self.processData("DetailHeader", data, level) self.processData("Detail", data, level) self.last_data_processed = data self.prev_level = level if not self._no_print_footer: for l in reversed(range(top_level + 1)): self.processData("DetailFooter", data, l) """ Paso intermedio que calcula si detailHeader + detail + detailFooter entran en el resto de la ṕagina. Si no es así crea nueva página. @param section_name. Nombre de la sección a procesar. @param data. Linea de datos a procesar. @param data_level. Nivel de seccion. """ def processData(self, section_name, data, data_level): self.actual_data_level = data_level listDF = self._xml.findall(section_name) data_size = len(listDF) for dF in listDF: draw_if = dF.get("DrawIf") show = True if draw_if: show = data.get(draw_if) if dF.get("Level") == str(data_level) and show not in ("None", "False"): if section_name in ("DetailHeader","Detail"): heightCalculated = self._parser_tools.getHeight(dF) + self.topSection() if section_name is "DetailHeader": for detail in self._xml.findall("Detail"): if detail.get("Level") == str(data_level): heightCalculated += self._parser_tools.getHeight(detail) for dFooter in self._xml.findall("DetailFooter"): if dFooter.get("Level") == str(data_level): heightCalculated += self._parser_tools.getHeight(dFooter) #for addFooter in self._xml.findall("AddOnFooter"): # if addFooter.get("Level") == str(data_level): # heightCalculated += self._parser_tools.getHeight(addFooter) pageFooter = self._xml.get("PageFooter") if pageFooter: if self._document.page_no() == 1 or pageFooter.get("PrintFrecuency") == "1": heightCalculated += self._parser_tools.getHeight(pageFooter) heightCalculated += self._bottom_margin if heightCalculated > self._document.h: # Si nos pasamos self._no_print_footer = True #Vemos el tope por abajo limit_bottom = self._document.h - self._parser_tools.getHeight(self._xml.get("AddOnFooter")) actual_size = self._parser_tools.getHeight(dF) + self.topSection() if (actual_size > limit_bottom + 2) or self.last_detail: # +2 se usa de margen extra self.processSection("AddOnFooter", str(data_level)) self.newPage(data_level) self.processXML(dF, data) if dF.get("NewPage") == "true" and not self.last_detail: self.newPage(data_level, False) break #Se ejecuta una sola instancia """ Procesa las secciones fuera de detail @param name. Nombre de la sección a procesar. """ def processSection(self, name, level=0): sec_list = self._xml.findall(name) sec_ = None for s in sec_list: if s.get("Level") == str(level) or s.get("Level") is None: sec_ = s if sec_ is not None: if sec_.get("PrintFrequency") == "1" or self._document.page_no() == 1 or name in ("AddOnHeader","AddOnFooter"): self.processXML(sec_) """ Procesa un elemento de xml. @param xml: El elemento a procesar. @param. data: Linea de datos afectada. """ def processXML(self, xml, data=None): fix_height = True if data is None: data = self._actual_data_line if self.design_mode: print("Procesando", xml.tag, data.get("level")) size_updated = False if xml.tag == "DetailFooter": if xml.get("PlaceAtBottom") == "true": self.setTopSection(self._document.h - self._parser_tools.getHeight(xml)) size_updated = True if xml.tag == "PageFooter": fix_height = False self.fix_extra_size() #Sirve para actualizar la altura con lineas que se han partido porque son muy largas for label in xml.iter("Label"): self.processText(label, data, fix_height) for field in xml.iter("Field"): self.processText(field, data, fix_height) for special in xml.iter("Special"): self.processText(special, data, fix_height, xml.tag) for calculated in xml.iter("CalculatedField"): self.processText(calculated, data, fix_height, xml.tag) #Busco draw_at_header en DetailFooter y los meto también if xml.tag == "DetailHeader": detail_level = xml.get("Level") for df in self._xml.iter("DetailFooter"): if df.get("Level") == detail_level: for cf in df.iter("CalculatedField"): if cf.get("DrawAtHeader") == "true": header_name = "%s_header_%s_%s" % (self.detailn[detail_level], detail_level, cf.get("Field")) self.draws_at_header[header_name] = "" self.processText(cf, data, fix_height, xml.tag) for line in xml.iter("Line"): self.processLine(line, fix_height) if not size_updated: self.setTopSection(self.topSection() + self._parser_tools.getHeight(xml)) def fix_extra_size(self): if self.increase_section_size > 0: self.setTopSection(self.topSection() + self.increase_section_size) self.increase_section_size = 0 """ Procesa una linea. @param xml. Sección de xml a procesar. @param fix_height. Ajusta la altura a los .kut originales, excepto el pageFooter. """ def processLine(self, xml, fix_height=True): color = xml.get("Color") r = 0 if not color else int(color.split(",")[0]) g = 0 if not color else int(color.split(",")[1]) b = 0 if not color else int(color.split(",")[2]) style = int(xml.get("Style")) width = int(xml.get("Width")) X1 = self.calculateLeftStart(xml.get("X1")) X1 = self.calculateWidth(X1, 0, False) X2 = self.calculateLeftStart(xml.get("X2")) X2 = self.calculateWidth(X2, 0, False) # Ajustar altura a secciones ya creadas Y1 = int(xml.get("Y1")) + self.topSection() Y2 = int(xml.get("Y2")) + self.topSection() if fix_height: Y1 = self._parser_tools.ratio_correction(Y1) Y2 = self._parser_tools.ratio_correction(Y2) self._document.set_line_width(width) self._document.set_draw_color(r, g, b) dash_length = 1 space_length = 1 if style == 2: dash_length = 20 space_length = 20 elif style == 3: dash_length = 10 space_length = 10 self._document.dashed_line(X1, Y1, X2, Y2, dash_length, space_length) #else: # self._document.line(X1, Y1, X2, Y2) """ Comprueba si excedemos el margen izquierdo de la página actual @param x. Posición a comprobar. @return Valor corregido, si procede. """ def calculateLeftStart(self, x): return self._parser_tools.ratio_correction(int(x)) + self._left_margin """ Comprueba si excedemos el margen derecho de la página actual @param x. Posición a comprobar. @return Valor corregido, si procede. """ def calculateWidth(self, width, pos_x, fix_ratio = True): width = int(width) if fix_ratio: width = self._parser_tools.ratio_correction(int(width)) ret_ = width limit = self._document.w - self._right_margin if pos_x + width > limit: ret_ = limit - pos_x return ret_ """ Procesa una etiqueta. Esta puede ser un campo calculado, una etiqueta, un campo especial o una imagen. @param xml. Sección de xml a procesar. @param fix_height. Ajusta la altura a los .kut originales, excepto el pageFooter. """ def processText(self, xml, data_row=None, fix_height=True, section_name = None): is_image = False is_barcode = False text = xml.get("Text") borderColor = xml.get("BorderColor") field_name = xml.get("Field") # x,y,W,H se calcula y corrigen aquí para luego estar correctos en los diferentes destinos posibles W = int(xml.get("Width")) H = self._parser_tools.getHeight(xml) x = int(xml.get("X")) y = int(xml.get("Y")) + self.topSection() # Añade la altura que hay ocupada por otras secciones if fix_height: y = self._parser_tools.ratio_correction(y) # Corrige la posición con respecto al kut original data_type = xml.get("DataType") if xml.tag == "Field" and data_row is not None: text = data_row.get(field_name) elif xml.tag == "Special": if text == "": if xml.get("Type") == "1": text = "PageNo" text = self._parser_tools.getSpecial( text, self._actual_append_page_no) calculation_type = xml.get("CalculationType") if calculation_type is not None and xml.tag != "Field": if calculation_type == "6": function_name = xml.get("FunctionName") try: nodo = self._parser_tools.convertToNode(data_row) from pineboolib.pncontrolsfactory import aqApp ret_ = aqApp.call(function_name, [nodo, field_name]) if ret_ is False: return else: text = str(ret_) except Exception: self.logger.exception( "KUT2FPDF:: Error llamando a function %s", function_name) return elif calculation_type == "1": text = self._parser_tools.calculate_sum(field_name, self.last_data_processed, self._xml_data, self.actual_data_level) elif calculation_type in ("5"): if data_row is None: data_row = self._xml_data[0] text = data_row.get(field_name) if data_type is not None: text = self._parser_tools.calculated(text, int(data_type), xml.get("Precision"), data_row) if data_type == "5": is_image = True elif data_type == "6": is_barcode = True if xml.get("BlankZero") == "1" and text is not None: res_ = re.findall(r'\d+', text) res_ = "".join(res_) if int(res_) == 0: return if text is not None and text.startswith(filedir("../tempdata")): is_image = True negValueColor = xml.get("NegValueColor") Currency = xml.get("Currency") commaSeparator = xml.get("CommaSeparator") dateFormat = xml.get("DateFormat") if is_image: self.draw_image(x, y, W, H, xml, text) elif is_barcode: self.draw_barcode(x, y, W, H, xml, text) else: level = data_row.get("level") if level and str(level) in self.detailn.keys(): val = "%s_header_%s_%s" % ( self.detailn[str(level)], level,field_name) if xml.get("DrawAtHeader") == "true" and level: if section_name == "DetailHeader": val = "" self.drawText(x, y, W, H, xml, val) print(level, section_name, val, text) if section_name == "DetailFooter" and xml.get("DrawAtHeader") == "true": self.draws_at_header[val] = text print("Añadiendo a", val, text, level) else: self.drawText(x, y, W, H, xml, text) """ Dibuja un campo texto en la página. @param x. Pos x de la etiqueta. @param y. Pos y de la etiqueta. @param W. Anchura de la etiqueta. @param H. Altura de la etiqueta. @param xml. Sección del xml afectada. @param txt. Texto calculado de la etiqueta a crear. """ def drawText(self, x, y, W, H, xml, txt): if txt in ("None", None): return txt = self._parser_tools.restore_text(txt) resizeable = False if xml.get("ChangeHeight") == "1": resizeable = True height_resized = False orig_x = x orig_y = y orig_W = W orig_H = H # Corregimos margenes: x = self.calculateLeftStart(x) W = self.calculateWidth(W, x) #bg_color = xml.get("BackgroundColor").split(",") fg_color = self.get_color(xml.get("ForegroundColor")) self._document.set_text_color(fg_color[0], fg_color[1], fg_color[2]) #self._document.set_draw_color(255, 255, 255) #if xml.get("BorderStyle") == "1": # FIXME: Hay que ajustar los margenes #font_name, font_size, font_style font_style = "" font_size = int(xml.get("FontSize")) font_name_orig = xml.get("FontFamily").lower() if xml.get("FontFamily") is not None else "helvetica" font_name = font_name_orig font_w = xml.get("FontWeight") if font_w in (None, "50"): #Normal font_w = 100 elif int(font_w) >= 65: font_style += "B" font_w = 100 fontI = xml.get("FontItalic") fontU = xml.get("FontUnderlined") # FIXME: hay que ver si es así background_color = self.get_color(xml.get("BackgroundColor")) #if background_color != [255,255,255]: #Los textos que llevan fondo no blanco van en negrita # font_style += "B" if fontI == "1": font_style += "I" if fontU == "1": font_style += "U" font_full_name = "%s%s" % (font_name, font_style) if font_full_name not in self._avalible_fonts: font_found = self._parser_tools.find_font(font_full_name) if font_found: self.logger.warning("KUT2FPDF::Añadiendo el tipo de letra %s (%s)", font_full_name, font_found) self._document.add_font(font_full_name, "", font_found, True) self._avalible_fonts.append(font_full_name) else: if font_full_name not in self._unavalible_fonts: self.logger.warning("KUT2FPDF:: No se encuentra el tipo de letra %s. Sustituido por helvetica%s." %(font_full_name, font_style)) self._unavalible_fonts.append(font_full_name) font_name = "helvetica" if font_name is not font_name_orig and font_name_orig.lower().find("narrow") > -1: font_w = 85 self._document.set_font(font_name, font_style, font_size) self._document.set_stretching(font_w) # Corregir alineación VAlignment = xml.get("VAlignment") # 0 izquierda, 1 centrado,2 derecha HAlignment = xml.get("HAlignment") layout_direction = xml.get("layoutDirection") start_section_size = self._actual_section_size result_section_size = 0 #Miramos si el texto sobrepasa el ancho array_text = [] array_n = [] text_lines = [] if txt.find("\n") > -1: for t in txt.split("\n"): array_n.append(t) if array_n: #Hay saltos de lineas ... for n in array_n: text_lines.append(n) else: #No hay saltos de lineas text_lines.append(txt) for tl in text_lines: str_width = self._document.get_string_width(tl) if str_width > W + 2 and xml.tag !="Label" and resizeable: #Una linea es mas larga que el ancho del campo(Dejando 2 de juego) height_resized = True array_text = self.split_text(tl, W) else: array_text.append(tl) #calculated_h = orig_H * len(array_text) self.drawRect(orig_x, orig_y, orig_W, orig_H, xml) processed_lines = 0 extra_size = 0 for actual_text in array_text: if actual_text is None: continue processed_lines += 1 if processed_lines > 1: extra_size += font_size + 2 if HAlignment == "1": # sobre X # Centrado x = x + (W / 2) - (self._document.get_string_width(actual_text) / 2) #x = x + (W / 2) - (str_width if not height_resized else W / 2) elif HAlignment == "2": # Derecha x = x + W - self._document.get_string_width(actual_text) - 2 # -2 de margen #x = x + W - str_width if not height_resized else W else: # Izquierda x = x + 2 if VAlignment == "1": # sobre Y # Centrado #y = (y + ((H / 2) / processed_lines)) + (((self._document.font_size_pt / 2) / 2) * processed_lines) y = ( orig_y + ( orig_H / 2))+ ((self._document.font_size_pt / 2) /2) elif VAlignment == "2": # Abajo y = orig_y + orig_H - font_size else: # Arriba y = orig_y + font_size y = y + extra_size if self.design_mode: self.write_debug(self.calculateLeftStart(orig_x), y, "Hal:%s, Val:%s, T:%s st:%s" % (HAlignment, VAlignment, txt, font_w), 6, "green") if xml.tag == "CalculatedField": self.write_debug(self.calculateLeftStart(orig_x), y, "CalculatedField:%s, Field:%s" % (xml.get("FunctionName"), xml.get("Field")), 3, "blue") self._document.text(x, y, actual_text) result_section_size += start_section_size result_section_size = result_section_size - start_section_size if self.increase_section_size < extra_size: #Si algun incremento extra hay superior se respeta self.increase_section_size = extra_size def split_text(self, texto, limit_w): list_ = [] linea_ = None for t in texto.split(" "): if linea_ is None and t == "": continue if linea_ is not None: if self._document.get_string_width(linea_ + t) > limit_w: list_.append(linea_) linea_ = "" else: linea_ = "" linea_ += "%s " % t list_.append(linea_) return list_ """ Dibuja un cuadrado en la página actual. @param x. Pos x del cuadrado. @param y. Pos y del cuadrado. @param W. Anchura del cuadrado. @param H. Altura del cuadrado. """ def get_color(self, value): value = value.split(",") r = None g = None b = None if len(value) == 3: r = int(value[0]) g = int(value[1]) b = int(value[2]) else: r = int(value[0:2]) g = int(value[3:5]) b = int(value[6:8]) return [r,g,b] def drawRect(self, x, y, W, H, xml = None): style_ = "" border_color = None bg_color = None line_width = self._document.line_width border_width = 0.2 #Calculamos borde y restamos del ancho orig_x = x orig_y = y orig_w = W x = self.calculateLeftStart(orig_x) W = self.calculateWidth(W, x) if xml is not None and not self.design_mode: if xml.get("BorderStyle") == "1": border_color = self.get_color(xml.get("BorderColor")) self._document.set_draw_color(border_color[0], border_color[1], border_color[2]) style_ += "D" bg_color = self.get_color(xml.get("BackgroundColor")) self._document.set_fill_color(bg_color[0], bg_color[1], bg_color[2]) style_ = "F" + style_ border_width = int(xml.get("BorderWidth") if xml.get("BorderWidth") else 0.2) else: self.write_cords_debug(x,y,W,H, orig_x, orig_w) style_ = "D" self._document.set_draw_color(0, 0, 0) if style_ is not "": self._document.set_line_width(border_width) self._document.rect(x, y, W, H, style_) self._document.set_line_width(line_width) self._document.set_xy(orig_x, orig_y) #self._document.set_draw_color(255, 255, 255) #self._document.set_fill_color(0, 0, 0) def write_cords_debug(self, x, y, w, h, ox, ow): self.write_debug(x,y,"X:%s Y:%s W:%s H:%s orig_x:%s, orig_W:%s" % (round(x, 2),round(y, 2),round(w, 2),round(h, 2), round(ox, 2), round(ow, 2)), 2, "red") def write_debug(self, x,y,text, h, color = None): orig_color = self._document.text_color r = None g = None b = None current_font_family = self._document.font_family current_font_size = self._document.font_size_pt current_font_style = self._document.font_style if color is "red": r = 255 g = 0 b = 0 elif color is "green": r = 0 g = 255 b = 0 elif color is "blue": r = 0 g = 0 b = 255 self._document.set_text_color(r,g,b) self._document.set_font_size(4) self._document.text(x, y + h, text) self._document.text_color = orig_color #self._document.set_xy(orig_x, orig_y) self._document.set_font(current_font_family, current_font_style, current_font_size) """ Inserta una imagen en la página actual. @param x. Pos x de la imagen. @param y. Pos y de la imagen. @param W. Anchura de la imagen. @param H. Altura de la imagen. @param xml. Sección del xml afectada. @param file_name. Nombr del fichero de tempdata a usar """ def draw_image(self, x, y, W, H, xml, file_name): import os if not file_name.lower().endswith(".png"): file_name = self._parser_tools.parseKey(file_name) if file_name is not None and os.path.exists(file_name): x = self.calculateLeftStart(x) W = self.calculateWidth(W, x) self._document.image(file_name, x, y, W, H, "PNG") def draw_barcode(self, x, y, W, H, xml, text): if text == "None": return from pineboolib.fllegacy.flcodbar import FLCodBar file_name = aqApp.tmp_dir() file_name += "/%s.png" % (text) type = xml.get("CodBarType") if not os.path.exists(file_name): bar_code = FLCodBar(text) #Code128 if type is not None: type = bar_code.nameToType(type.lower()) bar_code.setType(type) pix = bar_code.pixmap() if not pix.isNull(): pix.save(file_name, "PNG") self.draw_image(x , y, W, H, xml, file_name) """ Define los parámetros de la página @param xml: Elemento xml con los datos del fichero .kut a procesar """ def setPageFormat(self, xml): custom_size = None self._bottom_margin = int(xml.get("BottomMargin")) self._left_margin = int(xml.get("LeftMargin")) self._right_margin = int(xml.get("RightMargin")) self._top_margin = int(xml.get("TopMargin")) page_size = int(xml.get("PageSize")) page_orientation = xml.get("PageOrientation") if page_size in [30, 31]: custom_size = [int(xml.get("CustomHeightMM")), int(xml.get("CustomWidthMM"))] self._page_orientation = "P" if page_orientation == "0" else "L" self._page_size = self._parser_tools.converPageSize( page_size, int(page_orientation), custom_size) # devuelve un array def draw_margins(self): self.draw_debug_line(0 + self._left_margin , 0, 0+ self._left_margin , self._page_size[1]) #Vertical derecha self.draw_debug_line(self._page_size[0] - self._right_margin , 0, self._page_size[0] - self._right_margin , self._page_size[1])#Vertical izquierda self.draw_debug_line(0, 0 + self._top_margin, self._page_size[0], 0 + self._top_margin) #Horizontal superior self.draw_debug_line(0, self._page_size[1] - self._bottom_margin, self._page_size[0], self._page_size[1] - self._bottom_margin) #Horizontal inferior def draw_debug_line(self, X1, Y1, X2, Y2, title= None, color="GREY"): dash_length = 2 space_length = 2 r = 0 g = 0 b = 0 if color == "GREY": r = 220 g = 220 b = 220 self._document.set_line_width(1) self._document.set_draw_color(r, g, b) self._document.dashed_line(X1, Y1, X2, Y2, dash_length, space_length) def number_pages(self): return self._actual_append_page_no if self._actual_append_page_no > 0 else 0 def reset_page_no(self): self._actual_append_page_no = -1
class kut2fpdf(object): _document = None # Aquí se irán guardando los datos del documento logger = None _xml = None _xml_data = None _page_orientation = None _page_size = None _bottom_margin = None _left_margin = None _right_margin = None _top_margin = None _page_top = {} _data_row = None # Apunta a la fila actual en data _parser_tools = None _avalible_fonts = [] def __init__(self): self.logger = logging.getLogger("kut2rml") checkDependencies({"fpdf": "fpdf"}) from pineboolib.plugins.kugar.parsertools import parsertools self._parser_tools = parsertools() """ Convierte una cadena de texto que contiene el ".kut" en un pdf y retorna la ruta a este último. @param name. Nombre de ".kut". @param kut. Cadena de texto que contiene el ".kut". @param data. Cadena de texto que contiene los datos para ser rellenados en el informe. @return Ruta a fichero pdf. """ def parse(self, name, kut, data): try: self._xml = self._parser_tools.loadKut(kut) except Exception: self.logger.exception( "KUT2FPDF: Problema al procesar %s.kut", name) return False try: self._xml_data = load2xml(data) except Exception: self.logger.exception("KUT2FPDF: Problema al procesar xml_data") return False self.setPageFormat(self._xml) # self._page_orientation = # self._page_size = from fpdf import FPDF self._document = FPDF(self._page_orientation, "pt", self._page_size) # Seteamos rutas a carpetas con tipos de letra ... # Cargamos las fuentes disponibles for f in self._document.core_fonts: self.logger.debug("KUT2FPDF :: Adding font %s", f) self._avalible_fonts.append(f) self.newPage() self.processDetails() pdfname = pineboolib.project.getTempDir() pdfname += "/%s_%s.pdf" % (name, datetime.datetime.now().strftime("%Y%m%d%H%M%S")) # Datos del creador del documento self._document.set_title(name) self._document.set_author("Pineboo - kut2fpdf plugin") self._document.output(pdfname, 'F') return pdfname """ Indica el techo para calcular la posición de los objetos de esa sección. @return Número con el techo de la página actual. """ def topSection(self): return self._page_top[str(self._document.page_no())] """ Actualiza el valor del techo de la página actual. Se suele actualizar al procesar una sección. @param value. Numero que elspecifica el nuevo techo. """ def setTopSection(self, value): self._page_top[str(self._document.page_no())] = value """ Añade una nueva página al documento. """ def newPage(self): self._document.add_page(self._page_orientation) self._page_top[str(self._document.page_no())] = self._top_margin self._document.set_margins(self._left_margin, self._top_margin, self._right_margin) # Lo dejo pero no se nota nada # Corta con el borde inferior ... # self._document.set_auto_page_break( # True, self._document.h - self._bottom_margin) self.processSection("PageHeader") """ Procesa las secciones details con sus correspondientes detailHeader y detailFooter. """ def processDetails(self): # Procesamos la cabecera si procede .. prevLevel = 0 level = None for data in self._xml_data.findall("Row"): level = int(data.get("level")) if prevLevel > level: self.processData("DetailFooter", data, prevLevel) elif prevLevel < level: self.processData("DetailHeader", data, level) self.processData("Detail", data, level) prevLevel = level if level: for l in reversed(range(level + 1)): self.processData("DetailFooter", data, l) if self._xml.find("PageFooter"): self.processSection("PageFooter") elif self._xml.find("AddOnFooter"): self.processSection("AddOnFooter") """ Paso intermedio que calcula si detailHeader + detail + detailFooter entran en el resto de la ṕagina. Si no es así crea nueva página. @param section_name. Nombre de la sección a procesar. @param data. Linea de datos a procesar. @param data_level. Nivel de seccion. """ def processData(self, section_name, data, data_level): listDF = self._xml.findall(section_name) for dF in listDF: if dF.get("Level") == str(data_level): if section_name == "Detail" and (not dF.get("DrawIf") or data.get(dF.get("DrawIf"))): heightCalculated = self._parser_tools.getHeight(dF) + self.topSection() for dFooter in self._xml.findall("DetailFooter"): if dFooter.get("Level") == str(data_level): heightCalculated += self._parser_tools.getHeight(dFooter) pageFooter = self._xml.get("PageFooter") if pageFooter: if self._document.page_no() == 1 or pageFooter.get("PrintFrecuency") == "1": heightCalculated += self._parser_tools.getHeight(pageFooter) heightCalculated += self._bottom_margin if heightCalculated > self._document.h: # Si nos pasamos self.processSection("PageFooter") # Pie de página self.newPage() if not dF.get("DrawIf") or data.get(dF.get("DrawIf")): self.processXML(dF, data) #self.logger.debug("%s_BOTTON = %s" % (name.upper(), self.actualVSize[str(self.pagina)])) """ Procesa las secciones fuera de detail, pageHeader, pageFooter, AddOnFooter. @param name. Nombre de la sección a procesar. """ def processSection(self, name): sec_ = self._xml.find(name) if sec_: if sec_.get("PrintFrequency") == "1" or self._document.page_no() == 1: if sec_.tag == "PageFooter": self.setTopSection(self._document.h - int(sec_.get("Height"))) self.processXML(sec_) """ Procesa un elemento de xml. @param xml: El elemento a procesar. @param. data: Linea de datos afectada. """ def processXML(self, xml, data=None): fix_height = True if xml.tag == "DetailFooter": if xml.get("PlaceAtBottom") == "true": self.setTopSection(self.topSection() + self._parser_tools.getHeight(xml)) if xml.tag == "PageFooter": fix_height = False for child in xml.iter(): if child.tag in ("Label", "Field", "Special", "CalculatedField"): self.processText(child, data, fix_height) elif child.tag == "Line": self.processLine(child, fix_height) if xml.get("PlaceAtBottom") != "true": self.setTopSection(self.topSection() + self._parser_tools.getHeight(xml)) """ Procesa una linea. @param xml. Sección de xml a procesar. @param fix_height. Ajusta la altura a los .kut originales, excepto el pageFooter. """ def processLine(self, xml, fix_height=True): color = xml.get("Color") r = 0 if not color else int(color.split(",")[0]) g = 0 if not color else int(color.split(",")[1]) b = 0 if not color else int(color.split(",")[2]) #style = int(xml.get("Style")) width = int(xml.get("Width")) X1 = self.calculateLeftStart(xml.get("X1")) X2 = self.calculateRightEnd(xml.get("X2")) # Ajustar altura a secciones ya creadas Y1 = int(xml.get("Y1")) + self.topSection() Y2 = int(xml.get("Y2")) + self.topSection() if fix_height: Y1 = self._parser_tools.heightCorrection(Y1) Y2 = self._parser_tools.heightCorrection(Y2) self._document.set_line_width(width) self._document.set_draw_color(r, g, b) self._document.line(X1, Y1, X2, Y2) """ Comprueba si excedemos el margen izquierdo de la página actual @param x. Posición a comprobar. @return Valor corregido, si procede. """ def calculateLeftStart(self, x): x = int(x) ret_ = x if x < self._left_margin: ret_ = self._left_margin return ret_ """ Comprueba si excedemos el margen derecho de la página actual @param x. Posición a comprobar. @return Valor corregido, si procede. """ def calculateRightEnd(self, x): x = int(x) ret_ = x if x > (self._document.w - self._right_margin): ret_ = self._document.w - self._right_margin return ret_ """ Procesa una etiqueta. Esta peude ser un campo calculado, una etiqueta, un campo especial o una imagen. @param xml. Sección de xml a procesar. @param fix_height. Ajusta la altura a los .kut originales, excepto el pageFooter. """ def processText(self, xml, data_row=None, fix_height=True): isImage = False text = xml.get("Text") BorderWidth = int(xml.get("BorderWidth")) borderColor = xml.get("BorderColor") # x,y,W,H se calcula y corrigen aquí para luego estar correctos en los diferentes destinos posibles W = int(xml.get("Width")) H = self._parser_tools.getHeight(xml) x = int(xml.get("X")) y = int(xml.get("Y")) + self.topSection() # Añade la altura que hay ocupada por otras secciones if fix_height: y = self._parser_tools.heightCorrection(y) # Corrige la posición con respecto al kut original dataType = xml.get("Datatype") if xml.tag == "Field" and data_row is not None: text = data_row.get(xml.get("Field")) elif xml.tag == "Special": text = self._parser_tools.getSpecial( text[1:len(text) - 1], self._document.page_no()) elif xml.tag == "CalculatedField": if xml.get("FunctionName"): function_name = xml.get("FunctionName") try: nodo = self._parser_tools.convertToNode(data_row) text = str(pineboolib.project.call(function_name, [nodo])) except Exception: self.logger.exception( "KUT2FPDF:: Error llamando a function %s", function_name) return else: if data_row is None: data_row = self._xml_data[0] if xml.get("Field"): text = data_row.get( xml.get("Field")) if not "None" else "" if text and dataType is not None: text = self._parser_tools.calculated(text, int(dataType), xml.get("Precision"), data_row) if dataType == "5": isImage = True if text and text.startswith(filedir("../tempdata")): isImage = True precision = xml.get("Precision") negValueColor = xml.get("NegValueColor") Currency = xml.get("Currency") commaSeparator = xml.get("CommaSeparator") dateFormat = xml.get("DateFormat") if not isImage: self.drawText(x, y, W, H, xml, text) else: self.drawImage(x, y, W, H, xml, text) """ Dibuja un campo texto en la página. @param x. Pos x de la etiqueta. @param y. Pos y de la etiqueta. @param W. Anchura de la etiqueta. @param H. Altura de la etiqueta. @param xml. Sección del xml afectada. @param txt. Texto calculado de la etiqueta a crear. """ def drawText(self, x, y, W, H, xml, txt): if txt in ("None", None): return # Corregimos margenes: x = self.calculateLeftStart(x) W = self.calculateRightEnd(x + W) - x bg_color = xml.get("BackgroundColor").split(",") fg_color = xml.get("ForegroundColor").split(",") self._document.set_text_color(int(fg_color[0]), int(fg_color[1]), int(fg_color[2])) self._document.set_fill_color(int(bg_color[0]), int(bg_color[1]), int(bg_color[2])) if xml.get("BorderStyle") == "1": # FIXME: Hay que ajustar los margenes self.drawRect(x, y, W, H) #font_name, font_size, font_style font_style = "" font_size = int(xml.get("FontSize")) font_name = xml.get("FontFamily").lower() fontW = int(xml.get("FontWeight")) fontI = xml.get("FontItalic") fontU = xml.get("FontUnderlined") # FIXME: hay que ver si es así if fontW > 60 and font_size > 10: font_style += "B" if fontI == "1": font_style += "I" if fontU == "1": font_style += "U" while font_name not in self._avalible_fonts: font_found = self._parser_tools.find_font(font_name) if font_found: self.logger.info("KUT2FPDF::Añadiendo el tipo de letra %s (%s)", font_name, font_found) self._document.add_font(font_name, "", font_found, True) self._avalible_fonts.append(font_name) else: self.logger.warning("KUT2FPDF:: No se encuentra el tipo de letra %s. Sustituido por helvetica.", font_name) font_name = "helvetica" self._document.set_font(font_name, font_style, font_size) # Corregir alineación VAlignment = xml.get("VAlignment") # 0 izquierda, 1 centrado,2 derecha HAlignment = xml.get("HAlignment") if HAlignment == "1": # sobre X # Centrado x = x + (W / 2) - (self._document.get_string_width(txt) / 2) elif HAlignment == "2": # Derecha x = x + W - self._document.get_string_width(txt) else: # Izquierda x = x if VAlignment == "1": # sobre Y # Centrado y = (y + H / 2) + (self._document.font_size_pt / 2) elif VAlignment == "2": # Abajo y = y + W - font_size else: # Arriba y = y self._document.text(x, y, txt) """ Dibuja un cuadrado en la página actual. @param x. Pos x del cuadrado. @param y. Pos y del cuadrado. @param W. Anchura del cuadrado. @param H. Altura del cuadrado. """ def drawRect(self, x, y, W, H): self._document.rect(x, y, W, H, "DF") """ Inserta una imagen en la página actual. @param x. Pos x de la imagen. @param y. Pos y de la imagen. @param W. Anchura de la imagen. @param H. Altura de la imagen. @param xml. Sección del xml afectada. @param file_name. Nombr del fichero de tempdata a usar """ def drawImage(self, x, y, W, H, xml, file_name): self._document.image(file_name, x, y, W, H, "PNG") """ Define los parámetros de la página @param xml: Elemento xml con los datos del fichero .kut a procesar """ def setPageFormat(self, xml): custom_size = None self._bottom_margin = int(xml.get("BottomMargin")) self._left_margin = int(xml.get("LeftMargin")) self._right_margin = int(xml.get("RightMargin")) self._top_margin = int(xml.get("TopMargin")) page_size = int(xml.get("PageSize")) page_orientation = xml.get("PageOrientation") if page_size in [30, 31]: custom_size = [int(xml.get("CustomHeightMM")), int(xml.get("CustomWidthMM"))] self._page_orientation = "P" if page_orientation == "0" else "L" self._page_size = self._parser_tools.converPageSize( page_size, int(page_orientation), custom_size) # devuelve un array
def create_pdf2(self): # Set up a logo conn = sqlite3.connect("db_member.db") conn.row_factory = sqlite3.Row cur = conn.cursor() cur.execute("SELECT max(id) FROM member") rows = cur.fetchall() for row in rows: print("%s" % (row["max(id)"])) cur2 = conn.cursor() cur2.execute("SELECT name_pk FROM print_dt") cur3 = conn.cursor() cur3.execute("SELECT address FROM print_dt") cur4 = conn.cursor() cur4.execute("SELECT dt_name FROM print_dt") cur5 = conn.cursor() cur5.execute("SELECT * FROM `member`") rows5 = cur5.fetchall() rows4 = cur4.fetchall() rows3 = cur3.fetchall() rows2 = cur2.fetchall() for row5 in rows5: row5[6] for row2 in rows2: row2["name_pk"] for row3 in rows3: row3["address"] for row4 in rows4: row4["dt_name"] t = row2["name_pk"] t1 = row3["address"] t2 = "BS: " + row4["dt_name"] pdf = FPDF() pdf.set_font("Arial", size=12) pdf.add_page() pdf.image('demo.png', 8, 10, 25) pdf.add_font('DejaVu', '', 'DejaVuSerif-Italic.ttf', uni=True) pdf.set_font('DejaVu', '', 16) pdf.set_text_color(0, 70, 255) pdf.cell(35) pdf.cell(0, 5, t, ln=1) pdf.set_font('DejaVu', '', 14) pdf.cell(70) pdf.cell(0, 10, t2, ln=1) pdf.set_font('DejaVu', '', 14) pdf.set_text_color(0, 70, 255) pdf.cell(30) pdf.cell(0, 0, "ĐC:", ln=1) pdf.set_font('DejaVu', '', 12) pdf.set_text_color(0, 0, 0) pdf.cell(40) pdf.cell(0, 0, t1, ln=1) pdf.set_draw_color(0, 0, 0) pdf.set_line_width(1) pdf.line(30, 30, 180, 30) pdf.set_font('DejaVu', '', 16) pdf.set_text_color(255, 0, 40) pdf.cell(35) pdf.cell(0, 5, ' ', ln=1) pdf.set_font('DejaVu', '', 16) pdf.set_text_color(255, 0, 40) pdf.cell(35) pdf.cell(0, 15, 'PHIẾU KHÁM NỘI SOI TAI-MŨI-HỌNG', ln=1) pdf.set_font('DejaVu', '', 12) pdf.set_text_color(0, 70, 255) pdf.cell(75) pdf.cell(0, 0, 'Số Phiếu : ' + str(row5[0]), ln=1) pdf.set_font('DejaVu', '', 16) pdf.set_text_color(0, 70, 255) pdf.cell(0, 8, ' ', ln=1) pdf.set_font('DejaVu', '', 10) pdf.cell(5) pdf.cell(0, 0, 'Tên bệnh nhân : ' + str(row5[1]), ln=1) pdf.cell(85) pdf.cell(0, 0, 'Tuổi : ' + str(row5[4]), ln=1) pdf.cell(135) pdf.cell(0, 0, 'Giới tính : ' + str(row5[6]), ln=1) pdf.set_font('DejaVu', '', 10) pdf.set_text_color(0, 70, 255) pdf.cell(0, 8, ' ', ln=1) pdf.cell(5) pdf.cell(0, 0, 'Địa chỉ : ' + str(row5[3]), ln=1) pdf.cell(85) pdf.cell(0, 0, 'Số bảo hiểm : ' + str(row5[7]), ln=1) pdf.cell(135) pdf.cell(0, 0, 'Nghề nghiệp : ' + str(row5[2]), ln=1) pdf.set_font('DejaVu', '', 10) pdf.set_text_color(0, 70, 255) pdf.cell(0, 8, ' ', ln=1) pdf.cell(5) pdf.cell(0, 0, 'Triệu chứng : ' + str(row5[5]), ln=1) pdf.cell(85) pdf.cell(0, 0, 'Số bảo hiểm : ' + str(row5[7]), ln=1) pdf.cell(135) pdf.cell(0, 0, 'Nghề nghiệp : ' + str(row5[2]), ln=1) pdf.set_font('DejaVu', '', 14) pdf.cell(0, 15, ' ', ln=1) pdf.cell(70) pdf.cell(0, 0, 'HÌNH ẢNH NỘI SOI ', ln=1) # file_name = ('anh\%s.png' % ("a" + str(row["max(id)"]) + "a" + str(2))) file_name1 = ('anh\%s.png' % ("a" + str(row["max(id)"]) + "a" + str(3))) file_name2 = ('anh\%s.png' % ("a" + str(row["max(id)"]) + "a" + str(4))) file_name3 = ('anh\%s.png' % ("a" + str(row["max(id)"]) + "a" + str(5))) file_name4 = ('anh\%s.png' % ("a" + str(row["max(id)"]) + "a" + str(6))) file_name5 = ('anh\%s.png' % ("a" + str(row["max(id)"]) + "a" + str(7))) # pdf.image(file_name, 12, 90, 60) pdf.image(file_name1, 12, 150, 60) pdf.image(file_name2, 74, 90, 60) pdf.image(file_name3, 74, 150, 60) pdf.image(file_name4, 136, 90, 60) pdf.image(file_name5, 136, 150, 60) pdf.set_font('DejaVu', '', 16) pdf.cell(0, 132, ' ', ln=1) pdf.cell(60) pdf.cell(0, 0, 'MÔ TẢ KẾT QUẢ NỘI SOI ', ln=1) pdf.set_font('DejaVu', '', 12) pdf.cell(0, 8, ' ', ln=1) pdf.cell(12) pdf.cell(0, 7, 'Chẩn đoán : ', ln=1) pdf.cell(12) pdf.cell(0, 7, 'Điều trị : ', ln=1) pdf.cell(12) pdf.cell(0, 7, 'Chỉ định bác sĩ : ', ln=1) pdf.set_x(120) pdf.cell(0, 10, " Ngày " + str(today.day) + " Tháng " + str(today.month) + " Năm " + str(today.year), ln=1) pdf.set_x(145) pdf.cell(0, 6, 'Bác sĩ : ', ln=1) pdf.cell(0, 15, ' ', ln=1) pdf.set_x(126) pdf.cell(0, 0, t2, ln=1) directory1 = "doccument/" if not os.path.exists(directory1): os.makedirs(directory1) pdf.output('doccument\%s.pdf' %("a" + str(row["max(id)"]))) webbrowser.open_new(r'doccument\%s.pdf' %("a" + str(row["max(id)"]))) conn.commit() cur.close()
def invoice_to_PDF(order, client, filename=None): """ creates and pdf invoice of the order, and returns the pfd as a string buffer. @order: the order used to create a invoice @client: if specified this data is used for the client address, otherwise the address is taken from the order @filename: if specified the pdf is written to disk with the given filename, filename can contains a complete path """ pdf = FPDF() pdf.add_font('DejaVu', '', fname='{0}/fonts/ttf-dejavu/DejaVuSerif.ttf'.format( settings.STATIC_ROOT), uni=True) pdf.add_font('DejaVu', 'B', fname='{0}/fonts/ttf-dejavu/DejaVuSerif-Bold.ttf'.format( settings.STATIC_ROOT), uni=True) pdf.add_font('DejaVu', 'I', fname='{0}/fonts/ttf-dejavu/DejaVuSerif-Italic.ttf'.format( settings.STATIC_ROOT), uni=True) pdf.add_font( 'DejaVu', 'BI', fname='{0}/fonts/ttf-dejavu/DejaVuSerif-BoldItalic.ttf'.format( settings.STATIC_ROOT), uni=True) def check_for_new_page(): if pdf.get_y() > PAGE_BOTTOM_Y: pdf.add_page() def text_fonts(): pdf.set_font('DejaVu', '', 11.0) def add_row(column_data, columns=COLUMNS, border=0): """ add a row to the pdf """ cell_margin_left = 2 cell_margin_right = 2 check_for_new_page() last_y = START_TABLE_Y if pdf.page_no() == 1 else TOP_PAGE_MARGIN row_y = pdf.get_y() if pdf.get_y() > last_y else last_y max_y = row_y # max_y is used to check if multi cell is wrapping text and so uses more rows. next_x = LEFT_PAGE_MARGIN for i, column in enumerate(columns): width = column[0] align = column[1] pdf.set_xy(next_x + cell_margin_left, row_y) pdf.multi_cell(w=width - cell_margin_right, h=TABLE_ROW_HIGH, txt=column_data[i] if len(column_data) > i else '', align=align, border=border) max_y = max(max_y, pdf.get_y()) # keep track if multi cell wrapped text next_x += width pdf.set_y(max_y) def add_row_line(): last_y = START_TABLE_Y if pdf.page_no() == 1 else TOP_PAGE_MARGIN line_y = pdf.get_y() if pdf.get_y() > last_y else last_y - 2 pdf.set_xy(LEFT_PAGE_MARGIN + 1, line_y) pdf.line(LEFT_PAGE_MARGIN, line_y + 2, RIGHT_PAGE_MARGIN, line_y + 2) pdf.set_y(line_y + 5) def draw_vertical_lines_around_columns(columns=COLUMNS, top_y=TOP_PAGE_MARGIN, bottom_y=PAGE_BOTTOM_Y): #### # draw table vertical lines #### line_y = pdf.get_y() - 3 line_x = LEFT_PAGE_MARGIN first = True for column in columns: if first: pdf.line(line_x, START_TABLE_Y, line_x, line_y) # vertical line in front of table first = False line_x += column[0] pdf.line(line_x, START_TABLE_Y, line_x, line_y) # vertical line after column pdf.add_page() text_fonts() #### # Header: logo and company info #### pdf.image('{0}/gfx/logos/logo_jeslee.jpg'.format(settings.STATIC_ROOT), 20.0, TOP_PAGE_MARGIN, link='', type='', w=79.5, h=33.0) pdf.set_xy(125.0, TOP_PAGE_MARGIN) # pdf.set_font('arial', 'B', 13.0) pdf.set_left_margin(125.0) pdf.write( 6, '{company}\n{street}\n{zip} {city}\n{email}\n\nBank: {bank_account}\nKvk: {kvk}\nBTW: {btw}' .format(company=settings.COMPANY['business_name'], street=settings.COMPANY['street'], zip=settings.COMPANY['zip'], city=settings.COMPANY['city'], email=settings.COMPANY['email'], bank_account=settings.COMPANY['bank_account'], kvk=settings.COMPANY['kvk_nr'], btw=settings.COMPANY['btw_nr'])) #### # Invoice data #### pdf.set_xy(LEFT_PAGE_MARGIN, 75) pdf.set_left_margin(LEFT_PAGE_MARGIN) pdf.set_font('DejaVu', 'B', 14.0) pdf.write(6, 'Factuur {number}'.format(number=order.number)) text_fonts() pdf.set_xy(125, 75) pdf.write( 6, 'Factuurdatum: {date}'.format( date=datetime.now().strftime("%d %B %Y"))) pdf.set_xy(LEFT_PAGE_MARGIN, 85) pdf.write( 5, 'Referentie: {reference}'.format( reference=order.invoice_line2 if order. invoice_line2 else 'Uw bestelling bij Jeslee')) pdf.set_xy(LEFT_PAGE_MARGIN, 100) name = order.invoice_company_name \ if order.invoice_company_name \ else "{0} {1}".format(order.invoice_firstname, order.invoice_lastname) address = order.invoice_line1 pdf.write( 6, '{name}\n{address}\n{zip} {city}'.format(name=name.strip(), address=address.strip(), zip=order.invoice_code, city=order.invoice_city)) #### # Article data #### pdf.set_font('DejaVu', '', 9.0) add_row_line() add_row(['Artikelcode', 'Omschrijving', '', 'Bedrag\nincl. btw', 'Totaal']) add_row_line() for item in order.items.all(): code = '' description = '' if item.product: code = item.product.sku description = item.product.description if item.product.description else item.product.name str(item.product.tax if item.product.tax else '') add_row([ code, description, '%.0f x' % item.product_amount, to_currency(item.product_price_gross), to_currency(item.price_gross) ]) add_row_line() columns_below_items = [ (COLUMN_1_WIDTH + COLUMN_2_WIDTH + COLUMN_3_WIDTH + COLUMN_4_WIDTH, 'R'), (COLUMN_5_WIDTH, 'R') ] add_row(['Subtotaal', to_currency(order.price - order.tax)], columns=columns_below_items) taxes = tax_per_percentage(order) for tax_percentage, tax in taxes.iteritems(): add_row(['Btw {0}%'.format(tax_percentage), to_currency(tax)], columns=columns_below_items) add_row_line() pdf.set_font('DejaVu', 'B', 10.0) add_row(['Totaal', to_currency(order.price)], columns=columns_below_items) table_bottom = pdf.get_y() + 2 pdf.line(LEFT_PAGE_MARGIN, table_bottom, RIGHT_PAGE_MARGIN, table_bottom) # bottom horizontal line of table pdf.set_font('DejaVu', '', 10.0) pdf.set_y(pdf.get_y() + 10) check_for_new_page() pdf.set_x(LEFT_PAGE_MARGIN) pdf.multi_cell(w=COLUMN_1_WIDTH + COLUMN_2_WIDTH + COLUMN_3_WIDTH + COLUMN_4_WIDTH + COLUMN_5_WIDTH, h=TABLE_ROW_HIGH, txt=order.message, align='L', border=0) pdf.set_font('DejaVu', '', 10.0) pdf.set_xy(LEFT_PAGE_MARGIN, PAGE_BOTTOM_Y - 14) pay_date = datetime.now() + timedelta(PAY_INVOICE_WITHIN_DAYS) pdf.write( 4, 'We verzoeken u vriendelijk het bovenstaande bedrag van {order_price} voor ' '{pay_date} te voldoen op onze bankrekening onder vermelding van het ' 'factuurnummer {invoice_number}.\nVoor vragen kunt u contact opnemen per email ({email}).' .format(order_price=to_currency(order.price), pay_date=pay_date.strftime("%d %B %Y"), invoice_number=order.number, email=settings.COMPANY['email'])) pdf.set_draw_color(80) pdf.set_text_color(80) pdf.line(LEFT_PAGE_MARGIN, PAGE_BOTTOM_Y, RIGHT_PAGE_MARGIN, PAGE_BOTTOM_Y) # bottom horizontal line pdf.set_xy(LEFT_PAGE_MARGIN + 5, PAGE_BOTTOM_Y + 2) if filename: pdf.output(filename, 'F') return pdf.output('invoice.pdf', 'S')
class Kut2FPDF(object): """ Convert kuts to pyFPDF. """ _document: "FPDF" _xml: Element _xml_data: Element _page_orientation: str _page_size: List[int] _bottom_margin: int _left_margin: int _right_margin: int _top_margin: int _page_top: Dict[int, int] _data_row: Element _parser_tools: "kparsertools.KParserTools" _avalible_fonts: List[str] _unavalible_fonts: List[str] design_mode: bool _actual_data_line: Optional[Element] _no_print_footer: bool _actual_section_size: int increase_section_size: int last_detail: bool actual_data_level: int last_data_processed: Element prev_level: int draws_at_header: Dict[str, str] detailn: Dict[str, int] name_: str _actual_append_page_no: int reset_page_count: bool def __init__(self) -> None: """Constructor.""" check_dependencies({"fpdf": "fpdf2"}) self._parser_tools = kparsertools.KParserTools() self._avalible_fonts = [] self._page_top: Dict[int, int] = {} self._unavalible_fonts = [] self.design_mode = config.value("ebcomportamiento/kugar_debug_mode", False) self._actual_data_line = None self._no_print_footer = False self.increase_section_size = 0 self.actual_data_level = 0 self.prev_level = -1 self.draws_at_header = {} self.detailn = {} self.name_ = "" self._actual_append_page_no = -1 self.reset_page_count = False self.new_page = False def parse(self, name: str, kut: str, data: str, report: "FPDF" = None, flags: List[int] = []) -> Optional[str]: """ Parse string containing ".kut" file into a pdf and return its file path. @param name. Filename path for ".kut". @param kut. String with ".kut" file contents. @param data. String with data to be used in the report. @return Path to PDF file. """ try: self._xml = self._parser_tools.loadKut(kut).getroot() except Exception: LOGGER.exception("KUT2FPDF: Problema al procesar %s.kut", name) return None try: self._xml_data = load2xml(data).getroot() except Exception: LOGGER.exception("KUT2FPDF: Problema al procesar xml_data") return None application.PROJECT.message_manager().send( "progress_dialog_manager", "create", ["Pineboo", len(self._xml_data), "kugar"]) application.PROJECT.message_manager().send( "progress_dialog_manager", "setLabelText", ["Creando informe ...", "kugar"]) self.name_ = name self.setPageFormat(self._xml) # self._page_orientation = # self._page_size = if report is None: from fpdf import FPDF # type: ignore self._actual_append_page_no = 0 self._document = FPDF(self._page_orientation, "pt", self._page_size) for font in self._document.core_fonts: LOGGER.debug("KUT2FPDF :: Adding font %s", font) self._avalible_fonts.append(font) else: self._document = report # Seteamos rutas a carpetas con tipos de letra ... if not hasattr(self._document, "set_stretching"): raise Exception("incorrect pyfpdf versión , you need <= 1.7.3") # Cargamos las fuentes disponibles next_page_break = (flags[2] == 1) if len(flags) == 3 else True page_append = (flags[1] == 1) if len(flags) > 1 else False page_display = (flags[0] == 1) if len(flags) > 0 else False if page_append: self.prev_level = -1 self.last_detail = False page_break = False if self.new_page: page_break = True self.new_page = False if self.reset_page_count: self.reset_page_no() self.reset_page_count = False if self.design_mode: print("Append", page_append) print("Display", page_display) print("Page break", next_page_break) if next_page_break: self.reset_page_count = True if page_display: self.new_page = True self.processDetails(not page_break) # FIXME:Alguno valores no se encuentran for pages in self._document.pages.keys(): page_content = self._document.pages[pages]["content"] for header in self.draws_at_header.keys(): page_content = page_content.replace( header, str(self.draws_at_header[header])) self._document.pages[pages]["content"] = page_content # print(self.draws_at_header.keys()) self._document.set_title(self.name_) self._document.set_author("Pineboo - kut2fpdf plugin") return self._document def get_file_name(self) -> Optional[str]: """Retrieve file name where PDF should be saved.""" import os pdf_name = application.PROJECT.tmpdir pdf_name += "/%s_%s.pdf" % ( self.name_, datetime.datetime.now().strftime("%Y%m%d%H%M%S")) if os.path.exists(pdf_name): os.remove(pdf_name) if self._document is not None: self._document.output(pdf_name, "F") return pdf_name else: return None def topSection(self) -> int: """ Retrieve top section margin for current page to calculate object positions. @return Number with ceiling for current page. """ return self._page_top[int(self._document.page_no())] def setTopSection(self, value: int) -> None: """ Update top section for current page. Usually updated when processing a section. @param value. Number specifying new ceiling. """ self._actual_section_size = value - self.topSection() self._page_top[int(self._document.page_no())] = value def newPage(self, data_level: int, add_on_header: bool = True) -> None: """ Add a new page to the document. """ self._document.add_page(self._page_orientation) self._page_top[int(self._document.page_no())] = self._top_margin self._document.set_margins( self._left_margin, self._top_margin, self._right_margin) # Lo dejo pero no se nota nada self._no_print_footer = False if self.design_mode: self.draw_margins() self._actual_section_size = 0 self._actual_append_page_no += 1 if self.design_mode: print("Nueva página", self.number_pages()) # l_ini = data_level # l_end = self.prev_level # if l_ini == l_end: # l_end = l_end + 1 # if l_ini <= l_end: # for l in range(l_ini , l_end): # print(l) # self.processSection("AddOnHeader", str(l)) pg_headers = self._xml.findall("PageHeader") for page_header in pg_headers: if self.number_pages() == 1 or page_header.get( "PrintFrequency") == "1": ph_level = (page_header.get("Level") if page_header.get("Level") is not None else None) self.processSection("PageHeader", int(ph_level or "0")) break if add_on_header and not self.number_pages() == 1: for level in range(data_level + 1): self.processSection("AddOnHeader", int(level)) # Por ahora se omite detail header def processDetails(self, keep_page: Optional[bool] = None) -> None: """ Process detail secions with their matching detailHeader and detailFooter. """ # Procesamos la cabecera si procede .. top_level = 0 level = 0 first_page_created = (keep_page if keep_page is not None and self._document.page_no() > 0 else False) rows_array = self._xml_data.findall("Row") i = 0 for data in rows_array: self._actual_data_line = data level_str: Optional[str] = data.get("level") if level_str is None: level_str = "0" level = int(level_str) if level > top_level: top_level = level if not first_page_created: self.newPage(level) first_page_created = True if rows_array[len(rows_array) - 1] is data: self.last_detail = True if level < self.prev_level: for lev in range(level + 1, self.prev_level + 1): self.processData("DetailFooter", self.last_data_processed, lev) if not str(level) in self.detailn.keys(): self.detailn[str(level)] = 0 else: self.detailn[str(level)] += 1 if level > self.prev_level: self.processData("DetailHeader", data, level) self.processData("Detail", data, level) self.last_data_processed = data self.prev_level = level application.PROJECT.message_manager().send( "progress_dialog_manager", "setProgress", [i, "kugar"]) i += 1 if not self._no_print_footer and hasattr(self, "last_data_processed"): for lev in reversed(range(top_level + 1)): self.processData("DetailFooter", self.last_data_processed, lev) application.PROJECT.message_manager().send("progress_dialog_manager", "destroy", ["kugar"]) def processData(self, section_name: str, data: Element, data_level: int) -> None: """ Check if detailHeader + detail + detailFooter do fit in the remaining page and create a new page if not. @param section_name. Section name to check @param data. Data to check @param data_level. Section level """ self.actual_data_level = data_level list_sections = self._xml.findall(section_name) # data_size = len(listDF) for section in list_sections: draw_if = section.get("DrawIf") show = True if draw_if: show = bool(data.get(draw_if)) if section.get("Level") == str(data_level) and show not in ( "", "False", "None"): if section_name in ("DetailHeader", "Detail"): height_calculated = ( self._parser_tools.getHeight(section) + self.topSection() + self.increase_section_size) if section_name == "DetailHeader": for detail in self._xml.findall("Detail"): if detail.get("Level") == str(data_level): height_calculated += self._parser_tools.getHeight( detail) for detail_footer in self._xml.findall("DetailFooter"): if detail_footer.get("Level") == str(data_level): height_calculated += self._parser_tools.getHeight( detail_footer) aof_size = 0 for add_footer in self._xml.findall("AddOnFooter"): # if add_footer.get("Level") == str(data_level): aof_size += self._parser_tools.getHeight(add_footer) height_calculated += self._parser_tools.getHeight( add_footer) page_footer: Any = self._xml.get("PageFooter") if isinstance(page_footer, Element): if (self._document.page_no() == 1 or page_footer.get("PrintFrecuency") == "1"): height_calculated += self._parser_tools.getHeight( page_footer) height_calculated += self._bottom_margin if (height_calculated + aof_size) > self._document.h: # Si nos pasamos self._no_print_footer = True # Vemos el tope por abajo limit_bottom = self._document.h - aof_size actual_size = self._parser_tools.getHeight( section) + self.topSection() if (actual_size >= limit_bottom - 2 ) or self.last_detail: # +2 se usa de margen extra self.processSection("AddOnFooter", int(data_level)) self.newPage(data_level) self.processXML(section, data) if section.get("NewPage") == "true" and not self.last_detail: self.newPage(data_level, False) break # Se ejecuta una sola instancia def processSection(self, name: str, level: int = 0) -> None: """ Process non-detail sections. @param name. Section name to process """ sec_list = self._xml.findall(name) sec_ = None for section in sec_list: if section.get("Level") == str( level) or section.get("Level") is None: sec_ = section break if sec_ is not None: if (sec_.get("PrintFrequency") == "1" or self._document.page_no() == 1 or name in ("AddOnHeader", "AddOnFooter")): self.processXML(sec_) def processXML(self, xml: Element, data: Optional[Element] = None) -> None: """ Process single XML element. @param xml: Element to process @param. data: Line affected """ fix_height = True if data is None: data = self._actual_data_line if self.design_mode and data is not None: print("Procesando", xml.tag, data.get("level")) size_updated = False if xml.tag == "DetailFooter": if xml.get("PlaceAtBottom") == "true": height = self._parser_tools.getHeight(xml) self.setTopSection(self._document.h - height - self.increase_section_size) size_updated = True if xml.tag == "PageFooter": fix_height = False if not size_updated: self.fix_extra_size( ) # Sirve para actualizar la altura con lineas que se han partido porque son muy largas for label in xml.iter("Label"): self.processText(label, data, fix_height) for field in xml.iter("Field"): self.processText(field, data, fix_height) for special in xml.iter("Special"): self.processText(special, data, fix_height, xml.tag) for calculated in xml.iter("CalculatedField"): self.processText(calculated, data, fix_height, xml.tag) # Busco draw_at_header en DetailFooter y los meto también if xml.tag == "DetailHeader": detail_level = xml.get("Level") if detail_level is None: raise Exception("Level tag not found") for detail_footer in self._xml.iter("DetailFooter"): if detail_footer.get("Level") == detail_level: for calculated_filed in detail_footer.iter( "CalculatedField"): if calculated_filed.get("DrawAtHeader") == "true": header_name = "%s_header_%s_%s" % ( self.detailn[detail_level], detail_level, calculated_filed.get("Field"), ) self.draws_at_header[header_name] = "" self.processText(calculated_filed, data, fix_height, xml.tag) for line in xml.iter("Line"): self.processLine(line, fix_height) if not size_updated: self.setTopSection(self.topSection() + self._parser_tools.getHeight(xml)) def fix_extra_size(self) -> None: """Increase size of the section if needed.""" if self.increase_section_size > 0: self.setTopSection(self.topSection() + self.increase_section_size) self.increase_section_size = 0 def processLine(self, xml: Element, fix_height: bool = True) -> None: """ Process single line. @param xml. Sección de xml a procesar. @param fix_height. Ajusta la altura a los .kut originales, excepto el pageFooter. """ color = xml.get("Color") red = 0 if not color else int(color.split(",")[0]) green = 0 if not color else int(color.split(",")[1]) blue = 0 if not color else int(color.split(",")[2]) style = int(xml.get("Style") or "0") width = int(xml.get("Width") or "0") pos_x1 = self.calculateLeftStart(xml.get("X1") or "0") pos_x1 = self.calculateWidth(pos_x1, 0, False) pos_x2 = self.calculateLeftStart(xml.get("X2") or "0") pos_x2 = self.calculateWidth(pos_x2, 0, False) # Ajustar altura a secciones ya creadas pos_y1 = int(xml.get("Y1") or "0") + self.topSection() pos_y2 = int(xml.get("Y2") or "0") + self.topSection() if fix_height: pos_y1 = self._parser_tools.ratio_correction_h(pos_y1) pos_y2 = self._parser_tools.ratio_correction_h(pos_y2) self._document.set_line_width( self._parser_tools.ratio_correction_h(width)) self._document.set_draw_color(red, green, blue) dash_length = 1 space_length = 1 if style == 2: dash_length = 20 space_length = 20 elif style == 3: dash_length = 10 space_length = 10 self._document.dashed_line(pos_x1, pos_y1, pos_x2, pos_y2, dash_length, space_length) # else: # self._document.line(X1, Y1, X2, Y2) def calculateLeftStart(self, position: Union[str, int, float]) -> int: """ Check if left margin is exceeded for current page. @param position. Position to check. @return Revised position. """ return self._parser_tools.ratio_correction_w( int(position)) + self._left_margin def calculateWidth(self, width: int, pos_x: int, fix_ratio: bool = True) -> int: """ Check if right margin is exceeded for current page. @param x. Position to check. @return Revised position. """ limit = self._document.w - self._right_margin ret_: int if fix_ratio: width = self._parser_tools.ratio_correction_w(width) pos_x = self._parser_tools.ratio_correction_w(pos_x) ret_ = width if pos_x + width > limit: ret_ = limit - pos_x else: ret_ = width return ret_ def processText( self, xml: Element, data_row: Optional[Element] = None, fix_height: bool = True, section_name: Optional[str] = None, ) -> None: """ Check tag (calculated, label, special or image). @param xml. XML section to process. @param fix_height. Revise height from original .kut file except pageFooter. """ is_image = False is_barcode = False text: str = xml.get("Text") or "" # borderColor = xml.get("BorderColor") field_name = xml.get("Field") or "" # x,y,W,H se calcula y corrigen aquí para luego estar correctos en los diferentes destinos posibles width = int(xml.get("Width") or "0") height = self._parser_tools.getHeight(xml) pos_x = int(xml.get("X") or "0") pos_y = (int(xml.get("Y") or "0") + self.topSection() ) # Añade la altura que hay ocupada por otras secciones if fix_height: pos_y = self._parser_tools.ratio_correction_h( pos_y) # Corrige la posición con respecto al kut original data_type = xml.get("DataType") if xml.tag == "Field" and data_row is not None: text = data_row.get(field_name) or "" elif xml.tag == "Special": if text == "": if xml.get("Type") == "1": text = "PageNo" text = self._parser_tools.getSpecial(text, self._actual_append_page_no) calculation_type = xml.get("CalculationType") if calculation_type is not None and xml.tag != "Field": if calculation_type == "6": function_name = xml.get("FunctionName") if function_name and data_row is not None: try: nodo = self._parser_tools.convertToNode(data_row) ret_ = application.PROJECT.call( function_name, [nodo, field_name], None, False) if ret_ is False: return else: text = str(ret_) except Exception: LOGGER.exception( "KUT2FPDF:: Error llamando a function %s", function_name) return else: return elif calculation_type == "1": text = self._parser_tools.calculate_sum( field_name, self.last_data_processed, self._xml_data, self.actual_data_level) elif calculation_type in ("5"): if data_row is None: data_row = self._xml_data[0] text = data_row.get(field_name) or "" if data_type is not None: text = self._parser_tools.calculated(text, int(data_type), xml.get("Precision"), data_row) if data_type == "5": is_image = True elif data_type == "6": is_barcode = True if xml.get("BlankZero") == "1" and text is not None: res_ = re.findall(r"\d+", text) if res_ == ["0"]: return if text is not None: from pineboolib.core.settings import config temporal = config.value("ebcomportamiento/temp_dir") if text.startswith(temporal): is_image = True # negValueColor = xml.get("NegValueColor") # Currency = xml.get("Currency") # # commaSeparator = xml.get("CommaSeparator") # dateFormat = xml.get("DateFormat") if is_image: self.draw_image(pos_x, pos_y, width, height, xml, text) elif is_barcode: self.draw_barcode(pos_x, pos_y, width, height, xml, text) elif data_row is not None: level = data_row.get("level") or "0" if level and str(level) in self.detailn.keys(): val = "%s_header_%s_%s" % (self.detailn[str(level)], level, field_name) if xml.get("DrawAtHeader") == "true" and level: if section_name == "DetailHeader": val = "" self.drawText(pos_x, pos_y, width, height, xml, val) # print(level, section_name, val, text) if section_name == "DetailFooter" and xml.get( "DrawAtHeader") == "true": self.draws_at_header[val] = text # print("Añadiendo a", val, text, level) else: self.drawText(pos_x, pos_y, width, height, xml, text) def drawText(self, pos_x: int, pos_y: int, width: int, height: int, xml: Element, txt: str) -> None: """ Draw a text field onto the page. @param pos_x. Label X Pos. @param pos_y. Label Y Pos. @param width. Label Width. @param height. Label Height. @param xml. Related XML Section @param txt. Computed text of the label to be created. """ if txt in ("None", None): # return txt = "" if width == 0 and height == 0: return txt = self._parser_tools.restore_text(txt) resizeable = False if xml.get("ChangeHeight") == "1": resizeable = True # height_resized = False orig_x = pos_x orig_y = pos_y orig_w = width orig_h = height # Corregimos margenes: pos_x = self.calculateLeftStart(pos_x) width = self.calculateWidth(width, pos_x) # bg_color = xml.get("BackgroundColor").split(",") fg_color = self.get_color(xml.get("ForegroundColor") or "") self._document.set_text_color(fg_color[0], fg_color[1], fg_color[2]) # self._document.set_draw_color(255, 255, 255) # if xml.get("BorderStyle") == "1": # FIXME: Hay que ajustar los margenes # font_name, font_size, font_style font_style = "" font_size = int(xml.get("FontSize") or "0") font_name_orig = ((xml.get("FontFamily") or "").lower() if xml.get("FontFamily") is not None else "helvetica") font_name = font_name_orig font_w = int(xml.get("FontWeight") or "50") if font_w == 50: # Normal font_w = 100 elif font_w >= 65: font_style += "B" font_w = 100 font_italic = xml.get("FontItalic") font_underlined = xml.get( "FontUnderlined") # FIXME: hay que ver si es así # background_color = self.get_color(xml.get("BackgroundColor")) # if background_color != [255,255,255]: #Los textos que llevan fondo no blanco van en negrita # font_style += "B" if font_italic == "1": font_style += "I" if font_underlined == "1": font_style += "U" font_name = font_name.replace(" narrow", "") font_full_name = "%s%s" % (font_name, font_style) if font_full_name not in self._avalible_fonts: font_found: Optional[str] = None if font_full_name not in self._unavalible_fonts: font_found = self._parser_tools.find_font( font_full_name, font_style) if font_found: if self.design_mode: LOGGER.warning( "KUT2FPDF::Añadiendo el tipo de letra %s %s (%s)", font_name, font_style, font_found, ) self._document.add_font(font_name, font_style, font_found, True) self._avalible_fonts.append(font_full_name) else: if font_full_name not in self._unavalible_fonts: if self.design_mode: LOGGER.warning( "KUT2FPDF:: No se encuentra el tipo de letra %s. Sustituido por helvetica%s." % (font_full_name, font_style)) self._unavalible_fonts.append(font_full_name) font_name = "helvetica" if font_name is not font_name_orig and font_name_orig.lower().find( "narrow") > -1: font_w = 85 self._document.set_font(font_name, font_style, font_size) self._document.set_stretching(font_w) # Corregir alineación vertical_alignment = xml.get( "VAlignment") # 0 izquierda, 1 centrado,2 derecha horizontal_alignment = xml.get("HAlignment") # layout_direction = xml.get("layoutDirection") start_section_size = self._actual_section_size result_section_size = 0 # Miramos si el texto sobrepasa el ancho array_text: List[str] = [] array_n = [] text_lines = [] if txt.find("\n") > -1: for line_text in txt.split("\n"): array_n.append(line_text) if array_n: # Hay saltos de lineas ... for n in array_n: text_lines.append(n) else: # No hay saltos de lineas text_lines.append(txt) for text_line in text_lines: if len(text_line) > 1: if text_line[0] == " " and text_line[1] != " ": text_line = text_line[1:] str_width = self._document.get_string_width(text_line) if ( str_width > width - 10 and xml.tag != "Label" and resizeable ): # Una linea es mas larga que el ancho del campo(Le quito 10 al ancho maximo para que corte parecido a kugar original) # height_resized = True array_text = self.split_text(text_line, width - 10) else: array_text.append(text_line) # calculated_h = orig_h * len(array_text) self.drawRect(orig_x, orig_y, orig_w, orig_h, xml) processed_lines = 0 extra_size = 0 for actual_text in array_text: processed_lines += 1 if processed_lines > 1: extra_size += font_size + 2 if horizontal_alignment == "1": # sobre X # Centrado pos_x = pos_x + (width / 2) - ( self._document.get_string_width(actual_text) / 2) # x = x + (width / 2) - (str_width if not height_resized else width / 2) elif horizontal_alignment == "2": # Derecha pos_x = (pos_x + width - self._document.get_string_width(actual_text) - 2 ) # -2 de margen # x = x + width - str_width if not height_resized else width else: # Izquierda if processed_lines == 1: pos_x = pos_x + 2 if vertical_alignment == "1": # sobre Y # Centrado # y = (y + ((H / 2) / processed_lines)) + (((self._document.font_size_pt / 2) / 2) * processed_lines) pos_y = int((orig_y + (orig_h / 2)) + ((self._document.font_size_pt / 2) / 2)) if len(array_text) > 1: pos_y = pos_y - (font_size // 2) elif vertical_alignment == "2": # Abajo pos_y = orig_y + orig_h - font_size else: # Arriba pos_y = orig_y + font_size pos_y = pos_y + extra_size if self.design_mode: self.write_debug( self.calculateLeftStart(orig_x), pos_y, "Hal:%s, Val:%s, T:%s st:%s" % (horizontal_alignment, vertical_alignment, txt, font_w), 6, "green", ) if xml.tag == "CalculatedField": self.write_debug( self.calculateLeftStart(orig_x), pos_y, "CalculatedField:%s, Field:%s" % (xml.get("FunctionName"), xml.get("Field")), 3, "blue", ) self._document.text(pos_x, pos_y, actual_text) result_section_size += start_section_size result_section_size = result_section_size - start_section_size if (self.increase_section_size < extra_size ): # Si algun incremento extra hay superior se respeta self.increase_section_size = extra_size def split_text(self, texto: str, limit_w: int) -> List[str]: """Split text into lines based on visual width.""" list_ = [] linea_: Optional[str] = None for parte in texto.split(" "): if linea_ is None and parte == "": continue if linea_ is not None: if self._document.get_string_width(linea_ + parte) > limit_w: list_.append(linea_) linea_ = "" else: linea_ = "" linea_ += "%s " % parte if linea_ is not None: list_.append(linea_) return list_ def get_color(self, value: str) -> List[int]: """Convert color text into [r,g,b] array.""" lvalue = value.split(",") red: int green: int blue: int if len(lvalue) == 3: red = int(lvalue[0]) green = int(lvalue[1]) blue = int(lvalue[2]) else: red = int(value[0:2]) green = int(value[3:5]) blue = int(value[6:8]) return [red, green, blue] def drawRect(self, pos_x: int, pos_y: int, width: int, height: int, xml: Element = None) -> None: """ Draw a rectangle in current page. @param pos_x. left side @param pos_y. top side @param width. width @param height. heigth """ style_ = "" border_color = None bg_color = None line_width = self._document.line_width border_width = 0.2 # Calculamos borde y restamos del ancho orig_x = pos_x orig_y = pos_y orig_w = width pos_x = self.calculateLeftStart(orig_x) width = self.calculateWidth(width, pos_x) if xml is not None and not self.design_mode: if xml.get("BorderStyle") == "1": border_color = self.get_color(xml.get("BorderColor") or "") self._document.set_draw_color(border_color[0], border_color[1], border_color[2]) style_ += "D" bg_color = self.get_color(xml.get("BackgroundColor") or "") self._document.set_fill_color(bg_color[0], bg_color[1], bg_color[2]) style_ = "F" + style_ border_width = int( xml.get("BorderWidth") or "0" if xml.get("BorderWidth" ) else 0.2) else: self.write_cords_debug(pos_x, pos_y, width, height, orig_x, orig_w) style_ = "D" self._document.set_draw_color(0, 0, 0) if style_ != "": self._document.set_line_width(border_width) self._document.rect(pos_x, pos_y, width, height, style_) self._document.set_line_width(line_width) self._document.set_xy(orig_x, orig_y) # self._document.set_draw_color(255, 255, 255) # self._document.set_fill_color(0, 0, 0) def write_cords_debug( self, pos_x: Union[float, int], pos_y: Union[float, int], width: Union[float, int], height: Union[float, int], orig_x: Union[float, int], orig_w: Union[float, int], ) -> None: """Debug for Kut coordinated.""" self.write_debug( int(pos_x), int(pos_y), "X:%s Y:%s W:%s H:%s orig_x:%s, orig_W:%s" % ( round(pos_x, 2), round(pos_y, 2), round(width, 2), round(height, 2), round(orig_x, 2), round(orig_w, 2), ), 2, "red", ) def write_debug(self, pos_x: int, pos_y: int, text: str, height: int, color: Optional[str] = None) -> None: """Write debug data into the report.""" orig_color = self._document.text_color red = 0 green = 0 blue = 0 current_font_family = self._document.font_family current_font_size = self._document.font_size_pt current_font_style = self._document.font_style if color == "red": red = 255 elif color == "green": green = 255 elif color == "blue": blue = 255 self._document.set_text_color(red, green, blue) self._document.set_font_size(4) self._document.text(pos_x, pos_y + height, text) self._document.text_color = orig_color # self._document.set_xy(orig_x, orig_y) self._document.set_font(current_font_family, current_font_style, current_font_size) def draw_image(self, pos_x: int, pos_y: int, width: int, height: int, xml: Element, file_name: str) -> None: """ Draw image onto current page. @param pos_x. left position @param pos_y. top position @param width. width @param height. heigth @param xml. Related XML section @param file_name. filename of temp data to use """ import os if not file_name.lower().endswith(".png"): file_name_ = self._parser_tools.parseKey(file_name) if file_name_ is None: return file_name = file_name_ if os.path.exists(file_name): pos_x = self.calculateLeftStart(pos_x) width = self.calculateWidth(width, pos_x) self._document.image(file_name, pos_x, pos_y, width, height, "PNG") def draw_barcode(self, pos_x: int, pos_y: int, width: int, height: int, xml: Element, text: str) -> None: """ Draw barcode onto currrent page. """ if text == "None": return from pineboolib.fllegacy import flcodbar file_name = application.PROJECT.tmpdir file_name += "/%s.png" % (text) codbartype = xml.get("CodBarType") if not os.path.exists(file_name): bar_code = flcodbar.FLCodBar(text) # Code128 if codbartype is not None: type: int = bar_code.nameToType(codbartype.lower()) bar_code.setType(type) bar_code.setText(text) pix = bar_code.pixmap() if not pix.isNull(): pix.save(file_name, "PNG") self.draw_image(pos_x + 10, pos_y, width - 20, height, xml, file_name) def setPageFormat(self, xml: Element) -> None: """ Define page parameters. @param xml: XML with KUT data """ custom_size = None self._bottom_margin = int(xml.get("BottomMargin") or "0") self._left_margin = int(xml.get("LeftMargin") or "0") self._right_margin = int(xml.get("RightMargin") or "0") self._top_margin = int(xml.get("TopMargin") or "0") page_size = int(xml.get("PageSize") or "0") page_orientation = xml.get("PageOrientation") or "0" if page_size in [30, 31]: custom_size = [ int(xml.get("CustomHeightMM") or "0"), int(xml.get("CustomWidthMM") or "0"), ] self._page_orientation = "P" if page_orientation == "0" else "L" self._page_size = self._parser_tools.convertPageSize( page_size, int(page_orientation), custom_size) # devuelve un array def draw_margins(self) -> None: """Draw margins on the report.""" self.draw_debug_line(0 + self._left_margin, 0, 0 + self._left_margin, self._page_size[1]) # Vertical derecha self.draw_debug_line( self._page_size[0] - self._right_margin, 0, self._page_size[0] - self._right_margin, self._page_size[1], ) # Vertical izquierda self.draw_debug_line(0, 0 + self._top_margin, self._page_size[0], 0 + self._top_margin) # Horizontal superior self.draw_debug_line( 0, self._page_size[1] - self._bottom_margin, self._page_size[0], self._page_size[1] - self._bottom_margin, ) # Horizontal inferior def draw_debug_line( self, pos_x1: int, pos_y1: int, pos_x2: int, pos_y2: int, title: Optional[str] = None, color: str = "GREY", ) -> None: """Draw a debug line on the report.""" dash_length = 2 space_length = 2 red = 0 green = 0 blue = 0 if color == "GREY": red = 220 green = 220 blue = 220 self._document.set_line_width(1) self._document.set_draw_color(red, green, blue) self._document.dashed_line(pos_x1, pos_y1, pos_x2, pos_y1, dash_length, space_length) def number_pages(self) -> int: """Get number of pages on the report.""" return self._actual_append_page_no if self._actual_append_page_no > 0 else 0 def reset_page_no(self) -> None: """Reset page number.""" self._actual_append_page_no = -1
def invoice_to_PDF(order, client, filename=None): """ creates and pdf invoice of the order, and returns the pfd as a string buffer. @order: the order used to create a invoice @client: if specified this data is used for the client address, otherwise the address is taken from the order @filename: if specified the pdf is written to disk with the given filename, filename can contains a complete path """ pdf = FPDF() pdf.add_font('DejaVu', '', fname='{0}/fonts/ttf-dejavu/DejaVuSerif.ttf'.format(settings.STATIC_ROOT), uni=True) pdf.add_font('DejaVu', 'B', fname='{0}/fonts/ttf-dejavu/DejaVuSerif-Bold.ttf'.format(settings.STATIC_ROOT), uni=True) pdf.add_font('DejaVu', 'I', fname='{0}/fonts/ttf-dejavu/DejaVuSerif-Italic.ttf'.format(settings.STATIC_ROOT), uni=True) pdf.add_font('DejaVu', 'BI', fname='{0}/fonts/ttf-dejavu/DejaVuSerif-BoldItalic.ttf'.format(settings.STATIC_ROOT), uni=True) def check_for_new_page(): if pdf.get_y() > PAGE_BOTTOM_Y: pdf.add_page() def text_fonts(): pdf.set_font('DejaVu', '', 11.0) def add_row(column_data, columns=COLUMNS, border=0): """ add a row to the pdf """ cell_margin_left = 2 cell_margin_right = 2 check_for_new_page() last_y = START_TABLE_Y if pdf.page_no() == 1 else TOP_PAGE_MARGIN row_y = pdf.get_y() if pdf.get_y() > last_y else last_y max_y = row_y # max_y is used to check if multi cell is wrapping text and so uses more rows. next_x = LEFT_PAGE_MARGIN for i, column in enumerate(columns): width = column[0] align = column[1] pdf.set_xy(next_x+cell_margin_left, row_y) pdf.multi_cell(w=width-cell_margin_right, h=TABLE_ROW_HIGH, txt=column_data[i] if len(column_data) > i else '', align=align, border=border) max_y = max(max_y, pdf.get_y()) # keep track if multi cell wrapped text next_x += width pdf.set_y(max_y) def add_row_line(): last_y = START_TABLE_Y if pdf.page_no() == 1 else TOP_PAGE_MARGIN line_y = pdf.get_y() if pdf.get_y() > last_y else last_y - 2 pdf.set_xy(LEFT_PAGE_MARGIN + 1, line_y) pdf.line(LEFT_PAGE_MARGIN, line_y+2, RIGHT_PAGE_MARGIN, line_y+2) pdf.set_y(line_y+5) def draw_vertical_lines_around_columns(columns = COLUMNS, top_y=TOP_PAGE_MARGIN, bottom_y=PAGE_BOTTOM_Y): #### # draw table vertical lines #### line_y = pdf.get_y() - 3 line_x = LEFT_PAGE_MARGIN first = True for column in columns: if first: pdf.line(line_x, START_TABLE_Y, line_x, line_y) # vertical line in front of table first = False line_x += column[0] pdf.line(line_x, START_TABLE_Y, line_x, line_y) # vertical line after column pdf.add_page() text_fonts() #### # Header: logo and company info #### pdf.image('{0}/gfx/logos/logo_jeslee.jpg'.format(settings.STATIC_ROOT), 20.0, TOP_PAGE_MARGIN, link='', type='', w=79.5, h=33.0) pdf.set_xy(125.0, TOP_PAGE_MARGIN) # pdf.set_font('arial', 'B', 13.0) pdf.set_left_margin(125.0) pdf.write(6, '{company}\n{street}\n{zip} {city}\n{email}\n\nBank: {bank_account}\nKvk: {kvk}\nBTW: {btw}'.format( company=settings.COMPANY['business_name'], street=settings.COMPANY['street'], zip=settings.COMPANY['zip'], city=settings.COMPANY['city'], email=settings.COMPANY['email'], bank_account=settings.COMPANY['bank_account'], kvk=settings.COMPANY['kvk_nr'], btw=settings.COMPANY['btw_nr'] )) #### # Invoice data #### pdf.set_xy(LEFT_PAGE_MARGIN, 75) pdf.set_left_margin(LEFT_PAGE_MARGIN) pdf.set_font('DejaVu', 'B', 14.0) pdf.write(6, 'Factuur {number}'.format(number=order.number)) text_fonts() pdf.set_xy(125, 75) pdf.write(6, 'Factuurdatum: {date}'.format(date=datetime.now().strftime("%d %B %Y"))) pdf.set_xy(LEFT_PAGE_MARGIN, 85) pdf.write(5, 'Referentie: {reference}'.format(reference=order.invoice_line2 if order.invoice_line2 else 'Uw bestelling bij Jeslee')) pdf.set_xy(LEFT_PAGE_MARGIN, 100) name = order.invoice_company_name \ if order.invoice_company_name \ else "{0} {1}".format(order.invoice_firstname, order.invoice_lastname) address = order.invoice_line1 pdf.write(6, '{name}\n{address}\n{zip} {city}'.format( name=name.strip(), address=address.strip(), zip=order.invoice_code, city=order.invoice_city )) #### # Article data #### pdf.set_font('DejaVu', '', 9.0) add_row_line() add_row(['Artikelcode', 'Omschrijving', '', 'Bedrag\nincl. btw', 'Totaal']) add_row_line() for item in order.items.all(): code = '' description = '' if item.product: code = item.product.sku description = item.product.description if item.product.description else item.product.name str(item.product.tax if item.product.tax else '') add_row([code, description, '%.0f x' % item.product_amount, to_currency(item.product_price_gross), to_currency(item.price_gross)]) add_row_line() columns_below_items = [(COLUMN_1_WIDTH + COLUMN_2_WIDTH + COLUMN_3_WIDTH+COLUMN_4_WIDTH , 'R'), (COLUMN_5_WIDTH, 'R')] add_row(['Subtotaal', to_currency(order.price-order.tax)], columns=columns_below_items) taxes = tax_per_percentage(order) for tax_percentage, tax in taxes.iteritems(): add_row(['Btw {0}%'.format(tax_percentage), to_currency(tax)], columns=columns_below_items) add_row_line() pdf.set_font('DejaVu', 'B', 10.0) add_row(['Totaal', to_currency(order.price)], columns=columns_below_items) table_bottom = pdf.get_y() + 2 pdf.line(LEFT_PAGE_MARGIN, table_bottom, RIGHT_PAGE_MARGIN, table_bottom) # bottom horizontal line of table pdf.set_font('DejaVu', '', 10.0) pdf.set_y(pdf.get_y() + 10) check_for_new_page() pdf.set_x(LEFT_PAGE_MARGIN) pdf.multi_cell(w=COLUMN_1_WIDTH + COLUMN_2_WIDTH + COLUMN_3_WIDTH+COLUMN_4_WIDTH+COLUMN_5_WIDTH, h=TABLE_ROW_HIGH, txt=order.message, align='L', border=0) pdf.set_font('DejaVu', '', 10.0) pdf.set_xy(LEFT_PAGE_MARGIN, PAGE_BOTTOM_Y-14) pay_date = datetime.now() + timedelta(PAY_INVOICE_WITHIN_DAYS) pdf.write(4, 'We verzoeken u vriendelijk het bovenstaande bedrag van {order_price} voor ' '{pay_date} te voldoen op onze bankrekening onder vermelding van het ' 'factuurnummer {invoice_number}.\nVoor vragen kunt u contact opnemen per email ({email}).'. format(order_price=to_currency(order.price), pay_date=pay_date.strftime("%d %B %Y"), invoice_number=order.number, email=settings.COMPANY['email'])) pdf.set_draw_color(80) pdf.set_text_color(80) pdf.line(LEFT_PAGE_MARGIN, PAGE_BOTTOM_Y, RIGHT_PAGE_MARGIN, PAGE_BOTTOM_Y) # bottom horizontal line pdf.set_xy(LEFT_PAGE_MARGIN+5, PAGE_BOTTOM_Y+2) if filename: pdf.output(filename, 'F') return pdf.output('invoice.pdf', 'S')
from fpdf import FPDF pdf = FPDF('P', 'mm', 'A4') pdf.add_page() carros = [['HB20 1.0 Comfort Plus', 'Hyundai', '2014', 'R$ 33590,00'], ['2008 Allure 1.6 16v', 'Peugeot', '2018', 'R$ 71990,00'], ['Argo Drive 1.0 Firefly', 'Fiat', '2018', 'R$ 44999,00'], ['Corolla Sedan SEG 1.8 16v', 'Toyota', '2010', 'R$ 44900,00'], ['KA 1.0 SE', 'Ford', '2017', 'R$ 34900,00'], ['Fox 1.0 VHT', 'Volkswagen', '2013', 'R$ 26990,00']] pdf.set_font('Arial', 'B', 16) pdf.set_draw_color(51, 51, 255) pdf.set_fill_color(51, 51, 255) pdf.set_text_color(204, 255, 255) pdf.cell(0, 20, 'Loja - Carro de qualidade', 1, 1, 'C', 1) pdf.set_draw_color(255, 204, 51) pdf.set_fill_color(255, 204, 51) pdf.set_text_color(0, 0, 0) sub = '{:<26} {:<16} {:<5} {:<9}'.format('Carro', 'Marca', 'Ano', 'Valor') pdf.set_font('courier', 'B', 15) pdf.cell(0, 20, sub, 1, 1, 'R', 1) pdf.set_font('courier', '', 12) i = 0 for carro in carros:
def generate(transcripts, classID): pdf = FPDF() inp = [] if transcripts: inp = transcripts pdf.add_page() # image_count = 1 now = datetime.now() class_datestamp = f'Class Notes\n{classID} {now.strftime("%d/%m/%Y")}' pdf.set_font("Helvetica", size=normal_size) pdf.set_text_color(255) pdf.set_fill_color(r=13, g=40, b=76) pdf.multi_cell(w=0, txt=class_datestamp, align="C", h=normal_size / 2, fill=True) pdf.set_text_color(0) pdf.ln(h=padding) pdf.set_draw_color(r=13, g=40, b=76) for line in inp: line = line.strip() if isImportant(line, next_topic): pdf.ln(h=padding) pdf.set_text_color(255) pdf.set_font("Helvetica", style="B", size=topic_size) pdf.set_fill_color(r=13, g=40, b=76) pdf.multi_cell( txt=getImportantWord(line), w=pdf.get_string_width(getImportantWord(line) + " "), align="L", fill=True, h=topic_size / 2, ) pdf.set_text_color(0) pdf.ln(h=padding) elif isImportant(line, next_subtopic): pdf.ln(h=padding) pdf.set_text_color(r=13, g=40, b=76) pdf.set_font("Helvetica", style="B", size=subtopic_size) pdf.multi_cell( txt=getImportantWord(line), w=pdf.get_string_width(getImportantWord(line)) + 10, align="L", h=subtopic_size / 2, ) pdf.set_text_color(0) pdf.ln(h=padding) elif isImportant(line, image_trigger): pdf.set_font('Helvetica', size=topic_size) pdf.set_fill_color(167, 197, 238) pdf.set_text_color(r=13, g=40, b=76) pdf.multi_cell(txt="\nScreenshot here.\n\n", w=0, h=topic_size / 2, align="C", fill=True, border=1) pdf.cell(txt='', ln=1, w=0) pdf.set_text_color(0) pdf.ln(h=padding) # image_path = os.path.join( # "data", classID.lower(), f"image_{image_count}.png" # ) # if os.path.exists(image_path): # pdf.ln(h=padding) # pdf.image(image_path, w=190) # pdf.ln(h=padding) # image_count += 1 else: pdf.set_font("Helvetica", size=normal_size) pdf.multi_cell(txt=line, w=0, h=normal_size / 2) pdf.cell(txt="", ln=1, w=0) return pdf.output(dest="S").encode("latin-1")
def printwork(self): pdf = FPDF(orientation='P', unit='mm', format='A4') pdf.set_auto_page_break(auto=True, margin=10) pdf.add_page() pdf.set_image_filter("DCTDecode") # heading -> Personal Information pdf.set_font("times", 'B', size=12) pdf.set_draw_color(0, 0, 0) pdf.set_fill_color(0, 0, 0) pdf.set_text_color(255, 255, 255) pdf.set_line_width(1) pdf.cell(190, 5, txt="Personal Information", border=1, ln=1, align="L", fill=1) # content -> personal Information pdf.set_text_color(0, 0, 0) pdf.set_font("Helvetica", 'B', size=11) pdf.cell(30, 6, txt="ID:", ln=0, align="L") pdf.set_font("Helvetica", '', size=11) pdf.cell(50, 6, txt=str(self.id), ln=0, align='L') pdf.set_font("Helvetica", 'B', size=11) pdf.cell(30, 6, txt="Name:", ln=0, align="L") pdf.set_font("Helvetica", '', size=11) pdf.cell(50, 6, txt=self.name, ln=1, align='L') pdf.set_font("Helvetica", 'B', size=11) pdf.cell(30, 5, txt="Gender:", ln=0, align="L") pdf.set_font("Helvetica", '', size=11) pdf.cell(50, 5, txt=self.sex, ln=0, align='L') pdf.set_font("Helvetica", 'B', size=11) pdf.cell(30, 5, txt="Age:", ln=0, align="L") pdf.set_font("Helvetica", '', size=11) pdf.cell(50, 5, txt=self.age, ln=1, align='L') pdf.set_font("Helvetica", 'B', size=11) pdf.cell(30, 5, txt="Address:", ln=0, align="L") pdf.set_font("Helvetica", '', size=11) pdf.cell(50, 5, txt=self.address, ln=1, align='L') pdf.cell(190, 2, "", ln=1) # Heading -> C/C pdf.set_font("Helvetica", 'B', size=12) pdf.set_draw_color(0, 0, 0) pdf.set_fill_color(0, 0, 0) pdf.set_text_color(255, 255, 255) pdf.set_line_width(1) pdf.cell(190, 5, txt="C/C", border=1, ln=1, align="L", fill=1) # content -> c/c pdf.set_text_color(0, 0, 0) pdf.set_font("Helvetica", '', size=11) pdf.multi_cell(190, 5, txt=self.cc, ln=0, align="L") pdf.cell(190, 2, "", ln=1) # Heading -> OE pdf.set_font("Helvetica", 'B', size=12) pdf.set_draw_color(0, 0, 0) pdf.set_fill_color(0, 0, 0) pdf.set_text_color(255, 255, 255) pdf.set_line_width(1) pdf.cell(190, 5, txt="O.E.", border=1, ln=1, align="L", fill=1) # content -> OE pdf.set_text_color(0, 0, 0) pdf.set_font("Helvetica", '', size=11) pdf.multi_cell(190, 5, txt=self.oe, ln=0, align="L") pdf.cell(190, 2, "", ln=1) # Heading -> rf pdf.set_font("Helvetica", 'B', size=12) pdf.set_draw_color(0, 0, 0) pdf.set_fill_color(0, 0, 0) pdf.set_text_color(255, 255, 255) pdf.set_line_width(1) pdf.cell(190, 5, txt="R.F.", border=1, ln=1, align="L", fill=1) # content -> rf pdf.set_text_color(0, 0, 0) pdf.set_font("Helvetica", '', size=11) pdf.multi_cell(190, 5, txt=self.rf, ln=0, align="L") pdf.cell(190, 2, "", ln=1) # Heading -> path report pdf.set_font("Helvetica", 'B', size=12) pdf.set_draw_color(0, 0, 0) pdf.set_fill_color(0, 0, 0) pdf.set_text_color(255, 255, 255) pdf.set_line_width(1) pdf.cell(190, 5, txt="Pathology Reoprt", border=1, ln=1, align="L", fill=1) # content -> path report pdf.set_text_color(0, 0, 0) pdf.set_font("Helvetica", '', size=11) pdf.multi_cell(190, 5, txt=self.pathreport, ln=0, align="L") pdf.cell(190, 2, "", ln=1) # Heading -> Dxs pdf.set_font("Helvetica", 'B', size=12) pdf.set_draw_color(0, 0, 0) pdf.set_fill_color(0, 0, 0) pdf.set_text_color(255, 255, 255) pdf.set_line_width(1) pdf.cell(190, 5, txt="Dxs", border=1, ln=1, align="L", fill=1) # content -> Dxs pdf.set_text_color(0, 0, 0) pdf.set_font("Helvetica", '', size=11) pdf.multi_cell(190, 5, txt=self.dxs, ln=0, align="L") pdf.cell(190, 2, "", ln=1) # Heading -> comment pdf.set_font("Helvetica", 'B', size=12) pdf.set_draw_color(0, 0, 0) pdf.set_fill_color(0, 0, 0) pdf.set_text_color(255, 255, 255) pdf.set_line_width(1) pdf.cell(190, 5, txt="Comment", border=1, ln=1, align="L", fill=1) # content -> comment pdf.set_text_color(0, 0, 0) pdf.set_font("Helvetica", '', size=11) pdf.multi_cell(190, 5, txt=self.comments, ln=0, align="L") pdf.cell(190, 2, "", ln=1) curr_pos = pdf.get_y() if curr_pos + 55 > 275: pdf.add_page() pdf.set_font("times", 'B', size=12) pdf.set_draw_color(0, 0, 0) pdf.set_fill_color(0, 0, 0) pdf.set_text_color(255, 255, 255) pdf.set_line_width(1) pdf.cell(190, 5, txt="Images of Rediology", border=1, ln=1, align="L", fill=1) pdf.cell(190, 1, "", ln=1) i = 1 j = 0 while 2 * i <= len(self.rediology) + len(self.rediology) % 2: yy = pdf.get_y() if j + 1 <= len(self.rediology): pdf.image(self.path + self.rediology[j], x=10, h=50, w=90) j += 1 if j + 1 <= len(self.rediology): pdf.image(self.path + self.rediology[j], x=110, y=yy, h=50, w=90) j += 1 pdf.ln(2) i += 1 curr_pos = pdf.get_y() if curr_pos + 55 > 275: pdf.add_page() pdf.set_font("times", 'B', size=12) pdf.set_draw_color(0, 0, 0) pdf.set_fill_color(0, 0, 0) pdf.set_text_color(255, 255, 255) pdf.set_line_width(1) pdf.cell(190, 5, txt="Images of MRI", border=1, ln=1, align="L", fill=1) pdf.cell(190, 1, "", ln=1) i = 1 j = 0 while 2 * i <= len(self.mri) + len(self.mri) % 2: yy = pdf.get_y() if j + 1 <= len(self.mri): pdf.image(self.path + self.mri[j], x=10, h=50, w=90) j += 1 if j + 1 <= len(self.mri): pdf.image(self.path + self.mri[j], x=110, y=yy, h=50, w=90) j += 1 pdf.ln(2) i += 1 curr_pos = pdf.get_y() if curr_pos + 55 > 275: pdf.add_page() pdf.set_font("times", 'B', size=12) pdf.set_draw_color(0, 0, 0) pdf.set_fill_color(0, 0, 0) pdf.set_text_color(255, 255, 255) pdf.set_line_width(1) pdf.cell(190, 5, txt="Images of X-Ray", border=1, ln=1, align="L", fill=1) pdf.cell(190, 1, "", ln=1) i = 1 j = 0 while 2 * i <= len(self.xray) + len(self.xray) % 2: yy = pdf.get_y() if j + 1 <= len(self.xray): pdf.image(self.path + self.xray[j], x=10, h=50, w=90) j += 1 if j + 1 <= len(self.xray): pdf.image(self.path + self.xray[j], x=110, y=yy, h=50, w=90) j += 1 pdf.ln(2) i += 1 curr_pos = pdf.get_y() if curr_pos + 55 > 275: pdf.add_page() pdf.set_font("times", 'B', size=12) pdf.set_draw_color(0, 0, 0) pdf.set_fill_color(0, 0, 0) pdf.set_text_color(255, 255, 255) pdf.set_line_width(1) pdf.cell(190, 5, txt="Images of CT Scan", border=1, ln=1, align="L", fill=1) pdf.cell(190, 1, "", ln=1) i = 1 j = 0 while 2 * i <= len(self.ctscan) + len(self.ctscan) % 2: yy = pdf.get_y() if j + 1 <= len(self.ctscan): pdf.image(self.path + self.ctscan[j], x=10, h=50, w=90) j += 1 if j + 1 <= len(self.ctscan): pdf.image(self.path + self.ctscan[j], x=110, y=yy, h=50, w=90) j += 1 pdf.ln(2) i += 1 curr_pos = pdf.get_y() if curr_pos + 55 > 275: pdf.add_page() pdf.set_font("times", 'B', size=12) pdf.set_draw_color(0, 0, 0) pdf.set_fill_color(0, 0, 0) pdf.set_text_color(255, 255, 255) pdf.set_line_width(1) pdf.cell(190, 5, txt="Other Images", border=1, ln=1, align="L", fill=1) pdf.cell(190, 1, "", ln=1) i = 1 j = 0 while 2 * i <= len(self.pics) + len(self.pics) % 2: yy = pdf.get_y() if j + 1 <= len(self.pics): pdf.image(self.path + self.pics[j], x=10, h=50, w=90) j += 1 if j + 1 <= len(self.pics): pdf.image(self.path + self.pics[j], x=110, y=yy, h=50, w=90) j += 1 pdf.ln(2) i += 1 curr_pos = pdf.get_y() if curr_pos + 55 > 275: pdf.add_page() destination = QFileDialog.getExistingDirectory() pdf.output(destination + "/" + str(self.id) + ".pdf")
def to_pdf(self, **kwargs): from fpdf import FPDF import random paper_format = kwargs.get('paper_format', 'a4') paper = self.PAPER_SIZES[string.lower(paper_format)] font_scale = kwargs.get('font_scale', 1) font_name = kwargs.get('font_name') colorize = kwargs.get('colorize', False) if font_name is not None and not colorize: self.generate_luminosity_mapping(font_name) orientation = kwargs.get('orientation') if self.im.width > self.im.height: orientation = 'l' else: orientation = 'p' if orientation == 'l': paper.width, paper.height = paper.height, paper.width inner = Size(ceil(paper.width - self.margins.left - self.margins.right), ceil(paper.height - self.margins.top - self.margins.bottom)) imgpixels = Size(self.im.width, self.im.height) scale = min(inner.width, inner.height) / max(imgpixels.width, imgpixels.height) offset = Point(self.margins.left + (inner.width - imgpixels.width * scale) / 2, self.margins.bottom + (inner.height - imgpixels.height * scale) / 2) pdf = FPDF(unit='mm', format=paper_format.upper(), orientation=orientation.upper()) pdf.set_compression(True) pdf.set_title('ASCII Art') pdf.set_author('Oliver Lau <*****@*****.**> - Heise Medien GmbH & Co. KG') pdf.set_creator('asciifier') pdf.set_keywords('retro computing art fun') pdf.add_page() if font_name is not None: pdf.add_font(font_name, fname=font_name, uni=True) else: font_name = 'Courier' pdf.set_font(font_name, '', mm2pt(scale * font_scale)) for y in range(0, self.im.height): yy = offset.y + scale * y for x in range(0, self.im.width): c = self.result[x][y] if c != ' ': if colorize is True: r, g, b = self.im.getpixel((x, y)) pdf.set_text_color(r, g, b) pdf.text(offset.x + x * scale, yy, random.choice(Asciifier.COLOR_CHARS)) else: pdf.text(offset.x + x * scale, yy, c) crop_area = Margin(offset.y - scale, offset.x + (self.im.width - 1 + font_scale) * scale, offset.y + (self.im.height - 2 + font_scale) * scale, offset.x) if kwargs.get('cropmarks', False): pdf.set_draw_color(0, 0, 0) pdf.set_line_width(pt2mm(0.1)) for p in [Point(crop_area.left, crop_area.top), Point(crop_area.right, crop_area.top), Point(crop_area.right, crop_area.bottom), Point(crop_area.left, crop_area.bottom)]: pdf.line(p.x - 6, p.y, p.x - 2, p.y) pdf.line(p.x + 2, p.y, p.x + 6, p.y) pdf.line(p.x, p.y - 6, p.x, p.y - 2) pdf.line(p.x, p.y + 2, p.x, p.y + 6) if kwargs.get('logo'): logo_width = 20 pdf.image(kwargs.get('logo'), x=(crop_area.right - crop_area.left - logo_width / 2) / 2, y=crop_area.bottom + 10, w=logo_width) return pdf.output(dest='S')
# .cell(w,h,string,border,ln) pdf.cell(190, 10, 'Hello, World!', 1, 1, 'C') pdf.set_font("Courier", '', 14) pdf.cell(190, 10, 'printf("Hello, World!");', 'LTR', 1, 'C') # .set_text_color(r,g=-1,b=-1) pdf.set_text_color(198, 21, 21) pdf.cell(190, 10, 'print("Hello, World!")', "LRB", 1, 'C') pdf.set_font('Arial', 'B', 16) pdf.set_text_color(0) pdf.cell(95, 10, "C", 1, 0, 'C') # .set_draw_color(r,g,b) pdf.set_draw_color(198, 21, 21) # .set_line_width(size) in user defined unit pdf.set_line_width(1) pdf.cell(95, 10, "Python", 1, 0, 'C') pdf.set_draw_color(4, 139, 63) pdf.set_fill_color(4, 139, 63) pdf.line(10, 50, 200, 10) pdf.set_draw_color(4, 139, 63) pdf.set_fill_color(4, 139, 63) # .rect(x,y,w,h,style) pdf.rect(10, 60, 190, 10, 'DF') pdf.set_draw_color(198, 21, 21) pdf.set_fill_color(198, 21, 21)
class Report: def __init__(self, data): self.output_path = os.path.join(data.data_path, "stats") self.dataset_name = os.path.basename(data.data_path) self.mapi_light_light_green = [210, 245, 226] self.mapi_light_green = [5, 203, 99] self.mapi_light_grey = [218, 222, 228] self.mapi_dark_grey = [99, 115, 129] self.pdf = FPDF("P", "mm", "A4") self.pdf.add_page() self.title_size = 20 self.h1 = 16 self.h2 = 13 self.h3 = 10 self.text = 10 self.small_text = 8 self.margin = 10 self.cell_height = 7 self.total_size = 190 self.stats = self._read_stats_file("stats.json") def save_report(self, filename): self.pdf.output(os.path.join(self.output_path, filename), "F") def _make_table(self, columns_names, rows, row_header=False): self.pdf.set_font("Helvetica", "", self.h3) self.pdf.set_line_width(0.3) columns_sizes = [int(self.total_size / len(rows[0]))] * len(rows[0]) if columns_names: self.pdf.set_draw_color(*self.mapi_light_grey) self.pdf.set_fill_color(*self.mapi_light_grey) for col, size in zip(columns_names, columns_sizes): self.pdf.rect( self.pdf.get_x(), self.pdf.get_y(), size, self.cell_height, style="FD", ) self.pdf.set_text_color(*self.mapi_dark_grey) self.pdf.cell(size, self.cell_height, col, align="L") self.pdf.set_xy(self.margin, self.pdf.get_y() + self.cell_height) self.pdf.set_draw_color(*self.mapi_light_grey) self.pdf.set_fill_color(*self.mapi_light_light_green) for row in rows: for i, (col, size) in enumerate(zip(row, columns_sizes)): if i == 0 and row_header: self.pdf.set_draw_color(*self.mapi_light_grey) self.pdf.set_fill_color(*self.mapi_light_grey) self.pdf.rect( self.pdf.get_x(), self.pdf.get_y(), size, self.cell_height, style="FD", ) self.pdf.set_text_color(*self.mapi_dark_grey) if i == 0 and row_header: self.pdf.set_draw_color(*self.mapi_light_grey) self.pdf.set_fill_color(*self.mapi_light_light_green) self.pdf.cell(size, self.cell_height, col, align="L") self.pdf.set_xy(self.margin, self.pdf.get_y() + self.cell_height) def _read_stats_file(self, filename): file_path = os.path.join(self.output_path, filename) with io.open_rt(file_path) as fin: return io.json_load(fin) def _make_section(self, title): self.pdf.set_font("Helvetica", "B", self.h1) self.pdf.set_text_color(*self.mapi_dark_grey) self.pdf.cell(0, self.margin, title, align="L") self.pdf.set_xy(self.margin, self.pdf.get_y() + 1.5 * self.margin) def _make_subsection(self, title): self.pdf.set_xy(self.margin, self.pdf.get_y() - 0.5 * self.margin) self.pdf.set_font("Helvetica", "B", self.h2) self.pdf.set_text_color(*self.mapi_dark_grey) self.pdf.cell(0, self.margin, title, align="L") self.pdf.set_xy(self.margin, self.pdf.get_y() + self.margin) def _make_centered_image(self, image_path, desired_height): width, height = PIL.Image.open(image_path).size resized_width = width * desired_height / height if resized_width > self.total_size: resized_width = self.total_size desired_height = height * resized_width / width self.pdf.image( image_path, self.pdf.get_x() + self.total_size / 2 - resized_width / 2, self.pdf.get_y(), h=desired_height, ) self.pdf.set_xy(self.margin, self.pdf.get_y() + desired_height + self.margin) def make_title(self): # title self.pdf.set_font("Helvetica", "B", self.title_size) self.pdf.set_text_color(*self.mapi_light_green) self.pdf.cell(0, self.margin, "OpenSfM Quality Report", align="C") self.pdf.set_xy(self.margin, self.title_size) # version number try: out, _ = subprocess.Popen( ["git", "describe", "--tags"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, ).communicate() version = out.strip().decode() except BaseException as e: logger.warning( f"Exception thrwon while extracting 'git' version, {e}") version = "" # indicate we don't know the version version = "unknown" if version == "" else version self.pdf.set_font("Helvetica", "", self.small_text) self.pdf.set_text_color(*self.mapi_dark_grey) self.pdf.cell(0, self.margin, f"Processed with OpenSfM version {version}", align="R") self.pdf.set_xy(self.margin, self.pdf.get_y() + 2 * self.margin) def make_dataset_summary(self): self._make_section("Dataset Summary") rows = [ ["Dataset", self.dataset_name], ["Date", self.stats["processing_statistics"]["date"]], [ "Area Covered", f"{self.stats['processing_statistics']['area']/1e6:.6f} km²", ], [ "Processing Time", f"{self.stats['processing_statistics']['steps_times']['Total Time']:.2f} seconds", ], ] self._make_table(None, rows, True) self.pdf.set_xy(self.margin, self.pdf.get_y() + self.margin) def _has_meaningful_gcp(self): return (self.stats["reconstruction_statistics"]["has_gcp"] and "average_error" in self.stats["gcp_errors"]) def make_processing_summary(self): self._make_section("Processing Summary") rec_shots, init_shots = ( self.stats["reconstruction_statistics"] ["reconstructed_shots_count"], self.stats["reconstruction_statistics"]["initial_shots_count"], ) rec_points, init_points = ( self.stats["reconstruction_statistics"] ["reconstructed_points_count"], self.stats["reconstruction_statistics"]["initial_points_count"], ) geo_string = [] if self.stats["reconstruction_statistics"]["has_gps"]: geo_string.append("GPS") if self._has_meaningful_gcp(): geo_string.append("GCP") rows = [ [ "Reconstructed Images", f"{rec_shots} over {init_shots} shots ({rec_shots/init_shots*100:.1f}%)", ], [ "Reconstructed Points", f"{rec_points} over {init_points} points ({rec_points/init_points*100:.1f}%)", ], [ "Reconstructed Components", f"{self.stats['reconstruction_statistics']['components']} component", ], [ "Detected Features", f"{self.stats['features_statistics']['detected_features']['median']} features", ], [ "Reconstructed Features", f"{self.stats['features_statistics']['reconstructed_features']['median']} features", ], ["Geographic Reference", " and ".join(geo_string)], ] row_gps_gcp = [" / ".join(geo_string) + " errors"] geo_errors = [] if self.stats["reconstruction_statistics"]["has_gps"]: geo_errors.append( f"{self.stats['gps_errors']['average_error']:.2f}") if self._has_meaningful_gcp(): geo_errors.append( f"{self.stats['gcp_errors']['average_error']:.2f}") row_gps_gcp.append(" / ".join(geo_errors) + " meters") rows.append(row_gps_gcp) self._make_table(None, rows, True) self.pdf.set_xy(self.margin, self.pdf.get_y() + self.margin / 2) topview_height = 130 topview_grids = [ f for f in os.listdir(self.output_path) if f.startswith("topview") ] self._make_centered_image( os.path.join(self.output_path, topview_grids[0]), topview_height) self.pdf.set_xy(self.margin, self.pdf.get_y() + self.margin) def make_processing_time_details(self): self._make_section("Processing Time Details") columns_names = list( self.stats["processing_statistics"]["steps_times"].keys()) formatted_floats = [] for v in self.stats["processing_statistics"]["steps_times"].values(): formatted_floats.append(f"{v:.2f} sec.") rows = [formatted_floats] self._make_table(columns_names, rows) self.pdf.set_xy(self.margin, self.pdf.get_y() + 2 * self.margin) def make_gps_details(self): self._make_section("GPS/GCP Errors Details") # GPS for error_type in ["gps", "gcp"]: rows = [] columns_names = [error_type.upper(), "Mean", "Sigma", "RMS Error"] if "average_error" not in self.stats[error_type + "_errors"]: continue for comp in ["x", "y", "z"]: row = [comp.upper() + " Error (meters)"] row.append( f"{self.stats[error_type + '_errors']['mean'][comp]:.3f}") row.append( f"{self.stats[error_type +'_errors']['std'][comp]:.3f}") row.append( f"{self.stats[error_type +'_errors']['error'][comp]:.3f}") rows.append(row) rows.append([ "Total", "", "", f"{self.stats[error_type +'_errors']['average_error']:.3f}", ]) self._make_table(columns_names, rows) self.pdf.set_xy(self.margin, self.pdf.get_y() + self.margin / 2) self.pdf.set_xy(self.margin, self.pdf.get_y() + self.margin / 2) def make_features_details(self): self._make_section("Features Details") heatmap_height = 60 heatmaps = [ f for f in os.listdir(self.output_path) if f.startswith("heatmap") ] self._make_centered_image(os.path.join(self.output_path, heatmaps[0]), heatmap_height) if len(heatmaps) > 1: logger.warning("Please implement multi-model display") columns_names = ["", "Min.", "Max.", "Mean", "Median"] rows = [] for comp in ["detected_features", "reconstructed_features"]: row = [comp.replace("_", " ").replace("features", "").capitalize()] for t in columns_names[1:]: row.append( f"{self.stats['features_statistics'][comp][t.replace('.', '').lower()]:.0f}" ) rows.append(row) self._make_table(columns_names, rows) self.pdf.set_xy(self.margin, self.pdf.get_y() + self.margin) def make_reconstruction_details(self): self._make_section("Reconstruction Details") rows = [ [ "Average reprojection Error", f"{self.stats['reconstruction_statistics']['reprojection_error']:.2f} pixels", ], [ "Average Track Length", f"{self.stats['reconstruction_statistics']['average_track_length']:.2f} images", ], [ "Average Track Length (> 2)", f"{self.stats['reconstruction_statistics']['average_track_length_over_two']:.2f} images", ], ] self._make_table(None, rows, True) self.pdf.set_xy(self.margin, self.pdf.get_y() + self.margin) def make_camera_models_details(self): self._make_section("Camera Models Details") for camera, params in self.stats["camera_errors"].items(): residual_grids = [ f for f in os.listdir(self.output_path) if f.startswith("residuals_" + str(camera.replace("/", "_"))) ] if not residual_grids: continue initial = params["initial_values"] optimized = params["optimized_values"] names = [""] + list(initial.keys()) rows = [] rows.append(["Initial"] + [f"{x:.4f}" for x in initial.values()]) rows.append(["Optimized"] + [f"{x:.4f}" for x in optimized.values()]) self._make_subsection(camera) self._make_table(names, rows) self.pdf.set_xy(self.margin, self.pdf.get_y() + self.margin / 2) residual_grid_height = 80 self._make_centered_image( os.path.join(self.output_path, residual_grids[0]), residual_grid_height) def make_tracks_details(self): self._make_section("Tracks Details") matchgraph_height = 60 matchgraph = [ f for f in os.listdir(self.output_path) if f.startswith("matchgraph") ] self._make_centered_image( os.path.join(self.output_path, matchgraph[0]), matchgraph_height) histogram = self.stats["reconstruction_statistics"][ "histogram_track_length"] start_length, end_length = 2, 10 row_length = ["Length"] for length, _ in sorted(histogram.items(), key=lambda x: int(x[0])): if int(length) < start_length or int(length) > end_length: continue row_length.append(length) row_count = ["Count"] for length, count in sorted(histogram.items(), key=lambda x: int(x[0])): if int(length) < start_length or int(length) > end_length: continue row_count.append(f"{count}") self._make_table(None, [row_length, row_count], True) self.pdf.set_xy(self.margin, self.pdf.get_y() + self.margin) def add_page_break(self): self.pdf.add_page("P") def generate_report(self): self.make_title() self.make_dataset_summary() self.make_processing_summary() self.add_page_break() self.make_features_details() self.make_reconstruction_details() self.make_tracks_details() self.add_page_break() self.make_camera_models_details() self.make_gps_details() self.make_processing_time_details()
class Report: def __init__(self, data: DataSet, stats=None) -> None: self.output_path = os.path.join(data.data_path, "stats") self.dataset_name = os.path.basename(data.data_path) self.io_handler = data.io_handler self.mapi_light_light_green = [255, 255, 255] self.mapi_light_green = [0, 0, 0] self.mapi_light_grey = [218, 222, 228] self.mapi_dark_grey = [0, 0, 0] self.pdf = FPDF("P", "mm", "A4") self.pdf.add_page() self.title_size = 20 self.h1 = 16 self.h2 = 13 self.h3 = 10 self.text = 10 self.small_text = 8 self.margin = 10 self.cell_height = 7 self.total_size = 190 self.odm_stat = 'odm_processing_statistics' if stats is not None: self.stats = stats else: self.stats = self._read_stats_file("stats.json") def save_report(self, filename: str) -> None: # pyre-fixme[28]: Unexpected keyword argument `dest`. bytestring = self.pdf.output(dest="S") if isinstance(bytestring, str): bytestring = bytestring.encode("utf8") with self.io_handler.open(os.path.join(self.output_path, filename), "wb") as fwb: fwb.write(bytestring) def _make_table(self, columns_names, rows, row_header=False) -> None: self.pdf.set_font("Helvetica", "", self.h3) self.pdf.set_line_width(0.3) columns_sizes = [int(self.total_size / len(rows[0]))] * len(rows[0]) if columns_names: self.pdf.set_draw_color(*self.mapi_light_grey) self.pdf.set_fill_color(*self.mapi_light_grey) for col, size in zip(columns_names, columns_sizes): self.pdf.rect( self.pdf.get_x(), self.pdf.get_y(), size, self.cell_height, style="FD", ) self.pdf.set_text_color(*self.mapi_dark_grey) self.pdf.cell(size, self.cell_height, col, align="L") self.pdf.set_xy(self.margin, self.pdf.get_y() + self.cell_height) self.pdf.set_draw_color(*self.mapi_light_grey) self.pdf.set_fill_color(*self.mapi_light_light_green) for row in rows: for i, (col, size) in enumerate(zip(row, columns_sizes)): if i == 0 and row_header: self.pdf.set_draw_color(*self.mapi_light_grey) self.pdf.set_fill_color(*self.mapi_light_grey) self.pdf.rect( self.pdf.get_x(), self.pdf.get_y(), size, self.cell_height, style="FD", ) self.pdf.set_text_color(*self.mapi_dark_grey) if i == 0 and row_header: self.pdf.set_draw_color(*self.mapi_light_grey) self.pdf.set_fill_color(*self.mapi_light_light_green) self.pdf.cell(size, self.cell_height, col, align="L") self.pdf.set_xy(self.margin, self.pdf.get_y() + self.cell_height) def _read_stats_file(self, filename) -> Dict[str, Any]: file_path = os.path.join(self.output_path, filename) with self.io_handler.open_rt(file_path) as fin: return io.json_load(fin) def _read_gcp_stats_file(self, filename): file_path = os.path.join(self.output_path, "ground_control_points.json") with self.io_handler.open_rt(file_path) as fin: return io.json_load(fin) def _make_section(self, title: str) -> None: self.pdf.set_font("Helvetica", "B", self.h1) self.pdf.set_text_color(*self.mapi_dark_grey) self.pdf.cell(0, self.margin, title, align="L") self.pdf.set_xy(self.margin, self.pdf.get_y() + 1.5 * self.margin) def _make_subsection(self, title: str) -> None: self.pdf.set_xy(self.margin, self.pdf.get_y() - 0.5 * self.margin) self.pdf.set_font("Helvetica", "B", self.h2) self.pdf.set_text_color(*self.mapi_dark_grey) self.pdf.cell(0, self.margin, title, align="L") self.pdf.set_xy(self.margin, self.pdf.get_y() + self.margin) def _make_centered_image(self, image_path: str, desired_height: float) -> None: with tempfile.TemporaryDirectory() as tmp_local_dir: local_image_path = os.path.join(tmp_local_dir, os.path.basename(image_path)) with self.io_handler.open(local_image_path, "wb") as fwb: with self.io_handler.open(image_path, "rb") as f: fwb.write(f.read()) width, height = PIL.Image.open(local_image_path).size resized_width = width * desired_height / height if resized_width > self.total_size: resized_width = self.total_size desired_height = height * resized_width / width self.pdf.image( local_image_path, self.pdf.get_x() + self.total_size / 2 - resized_width / 2, self.pdf.get_y(), h=desired_height, ) self.pdf.set_xy(self.margin, self.pdf.get_y() + desired_height + self.margin) def make_title(self) -> None: # title self.pdf.set_font("Helvetica", "B", self.title_size) self.pdf.set_text_color(*self.mapi_light_green) self.pdf.cell(0, self.margin, "ODM Quality Report", align="C") self.pdf.set_xy(self.margin, self.title_size) # version number version_file = os.path.abspath( os.path.join(os.path.dirname(__file__), "../../../../../VERSION")) try: with open(version_file, 'r') as f: version = f.read().strip() except Exception as e: # indicate we don't know the version version = "unknown" logger.warning(f"Invalid ODM version {version_file}: " + str(e)) self.pdf.set_font("Helvetica", "", self.small_text) self.pdf.set_text_color(*self.mapi_dark_grey) self.pdf.cell(0, self.margin, f"Processed with ODM version {version}", align="R") self.pdf.set_xy(self.margin, self.pdf.get_y() + 2 * self.margin) def make_dataset_summary(self) -> None: self._make_section("Dataset Summary") rows = [ #["Dataset", self.dataset_name], ["Date", self.stats["processing_statistics"]["date"]], [ "Area Covered", f"{self.stats['processing_statistics']['area']/1e6:.6f} km²", ], [ "Processing Time", self.stats[self.odm_stat]['total_time_human'] if self.odm_stat in self.stats else \ f"{self.stats['processing_statistics']['steps_times']['Total Time']:.2f} seconds", ], ["Capture Start", self.stats["processing_statistics"]["start_date"]], ["Capture End", self.stats["processing_statistics"]["end_date"]], ] self._make_table(None, rows, True) self.pdf.set_xy(self.margin, self.pdf.get_y() + self.margin) def _has_meaningful_gcp(self) -> bool: return (self.stats["reconstruction_statistics"]["has_gcp"] and "average_error" in self.stats["gcp_errors"]) def make_processing_summary(self) -> None: self._make_section("Processing Summary") rec_shots, init_shots = ( self.stats["reconstruction_statistics"] ["reconstructed_shots_count"], self.stats["reconstruction_statistics"]["initial_shots_count"], ) rec_points, init_points = ( self.stats["reconstruction_statistics"] ["reconstructed_points_count"], self.stats["reconstruction_statistics"]["initial_points_count"], ) geo_string = [] if self.stats["reconstruction_statistics"]["has_gps"]: geo_string.append("GPS") if self._has_meaningful_gcp(): geo_string.append("GCP") ratio_shots = rec_shots / init_shots * 100 if init_shots > 0 else -1 rows = [ [ "Reconstructed Images", f"{rec_shots} over {init_shots} shots ({ratio_shots:.1f}%)", ], [ "Reconstructed Points (Sparse)", f"{rec_points} over {init_points} points ({rec_points/init_points*100:.1f}%)", ], # [ # "Reconstructed Components", # f"{self.stats['reconstruction_statistics']['components']} component", # ], [ "Detected Features", f"{self.stats['features_statistics']['detected_features']['median']:,} features", ], [ "Reconstructed Features", f"{self.stats['features_statistics']['reconstructed_features']['median']:,} features", ], ["Geographic Reference", " and ".join(geo_string)], ] # Dense (if available) if self.stats.get('point_cloud_statistics'): if self.stats['point_cloud_statistics'].get('dense'): rows.insert(2, [ "Reconstructed Points (Dense)", f"{self.stats['point_cloud_statistics']['stats']['statistic'][0]['count']:,} points" ]) # GSD (if available) if self.odm_stat in self.stats and self.stats[self.odm_stat].get( 'average_gsd'): rows.insert(3, [ "Average Ground Sampling Distance (GSD)", f"{self.stats[self.odm_stat]['average_gsd']:.1f} cm" ]) row_gps_gcp = [" / ".join(geo_string) + " errors"] geo_errors = [] if self.stats["reconstruction_statistics"]["has_gps"]: geo_errors.append( f"{self.stats['gps_errors']['average_error']:.2f}") if self._has_meaningful_gcp(): geo_errors.append( f"{self.stats['gcp_errors']['average_error']:.2f}") row_gps_gcp.append(" / ".join(geo_errors) + " meters") rows.append(row_gps_gcp) self._make_table(None, rows, True) self.pdf.set_xy(self.margin, self.pdf.get_y() + self.margin / 2) topview_height = 110 topview_grids = [ f for f in self.io_handler.ls(self.output_path) if f.startswith("topview") ] self._make_centered_image( os.path.join(self.output_path, topview_grids[0]), topview_height) self.pdf.set_xy(self.margin, self.pdf.get_y() + self.margin) def make_processing_time_details(self) -> None: self._make_section("Processing Time Details") columns_names = list( self.stats["processing_statistics"]["steps_times"].keys()) formatted_floats = [] for v in self.stats["processing_statistics"]["steps_times"].values(): formatted_floats.append(f"{v:.2f} sec.") rows = [formatted_floats] self._make_table(columns_names, rows) self.pdf.set_xy(self.margin, self.pdf.get_y() + 2 * self.margin) def make_gcp_error_details(self): self._make_section("Ground Control Point Error") gcp_stats = self._read_gcp_stats_file("ground_control_points.json") rows = [] column_names = ["ID", "Error X (m)", "Error Y (m)", "Error Z (m)"] for gcp in gcp_stats: row = [gcp["id"]] row.append(f"{gcp['error'][0]:.3f}") row.append(f"{gcp['error'][1]:.3f}") row.append(f"{gcp['error'][2]:.3f}") rows.append(row) self._make_table(column_names, rows) self.pdf.set_xy(self.margin, self.pdf.get_y() + self.margin / 2) def make_gps_details(self) -> None: self._make_section("GPS/GCP/3D Errors Details") # GPS table_count = 0 for error_type in ["gps", "gcp", "3d"]: rows = [] columns_names = [error_type.upper(), "Mean", "Sigma", "RMS Error"] if "average_error" not in self.stats[error_type + "_errors"]: continue for comp in ["x", "y", "z"]: row = [comp.upper() + " Error (meters)"] row.append( f"{self.stats[error_type + '_errors']['mean'][comp]:.3f}") row.append( f"{self.stats[error_type +'_errors']['std'][comp]:.3f}") row.append( f"{self.stats[error_type +'_errors']['error'][comp]:.3f}") rows.append(row) rows.append([ "Total", "", "", f"{self.stats[error_type +'_errors']['average_error']:.3f}", ]) self._make_table(columns_names, rows) self.pdf.set_xy(self.margin, self.pdf.get_y() + self.margin / 2) table_count += 1 if table_count > 0: abs_error_type = "gps" if table_count == 2 else "gcp" a_ce90 = self.stats[abs_error_type + "_errors"].get("ce90", 0) a_le90 = self.stats[abs_error_type + "_errors"].get("le90", 0) r_ce90 = self.stats["3d_errors"].get("ce90", 0) r_le90 = self.stats["3d_errors"].get("le90", 0) rows = [] if a_ce90 > 0 and a_le90 > 0: rows += [[ "Horizontal Accuracy CE90 (meters)", f"{a_ce90:.3f}", f"{r_ce90:.3f}" if r_ce90 > 0 else "-", ], [ "Vertical Accuracy LE90 (meters)", f"{a_le90:.3f}", f"{r_le90:.3f}" if r_le90 > 0 else "-", ]] if rows: if table_count > 2: self.add_page_break() self._make_table(["", "Absolute", "Relative"], rows, True) self.pdf.set_xy(self.margin, self.pdf.get_y() + self.margin / 2) # rows = [] # columns_names = [ # "GPS Bias", # "Scale", # "Translation", # "Rotation", # ] # for camera, params in self.stats["camera_errors"].items(): # bias = params["bias"] # s, t, R = bias["scale"], bias["translation"], bias["rotation"] # rows.append( # [ # camera, # f"{s:.2f}", # f"{t[0]:.2f} {t[1]:.2f} {t[2]:.2f}", # f"{R[0]:.2f} {R[1]:.2f} {R[2]:.2f}", # ] # ) # self._make_table(columns_names, rows) self.pdf.set_xy(self.margin, self.pdf.get_y() + self.margin / 2) def make_features_details(self) -> None: self._make_section("Features Details") heatmap_height = 60 heatmaps = [ f for f in self.io_handler.ls(self.output_path) if f.startswith("heatmap") ] self._make_centered_image(os.path.join(self.output_path, heatmaps[0]), heatmap_height) if len(heatmaps) > 1: logger.warning("Please implement multi-model display") columns_names = ["", "Min.", "Max.", "Mean", "Median"] rows = [] for comp in ["detected_features", "reconstructed_features"]: row = [comp.replace("_", " ").replace("features", "").capitalize()] for t in columns_names[1:]: row.append( f"{self.stats['features_statistics'][comp][t.replace('.', '').lower()]:.0f}" ) rows.append(row) self._make_table(columns_names, rows) self.pdf.set_xy(self.margin, self.pdf.get_y() + self.margin) def make_reconstruction_details(self) -> None: self._make_section("Reconstruction Details") rows = [ [ "Average Reprojection Error (normalized / pixels / angular)", (f"{self.stats['reconstruction_statistics']['reprojection_error_normalized']:.2f} / " f"{self.stats['reconstruction_statistics']['reprojection_error_pixels']:.2f} / " f"{self.stats['reconstruction_statistics']['reprojection_error_angular']:.5f}" ), ], [ "Average Track Length", f"{self.stats['reconstruction_statistics']['average_track_length']:.2f} images", ], [ "Average Track Length (> 2)", f"{self.stats['reconstruction_statistics']['average_track_length_over_two']:.2f} images", ], ] self._make_table(None, rows, True) self.pdf.set_xy(self.margin, self.pdf.get_y() + self.margin / 1.5) residual_histogram_height = 60 residual_histogram = [ f for f in self.io_handler.ls(self.output_path) if f.startswith("residual_histogram") ] self._make_centered_image( os.path.join(self.output_path, residual_histogram[0]), residual_histogram_height, ) self.pdf.set_xy(self.margin, self.pdf.get_y() + self.margin) def make_camera_models_details(self) -> None: self._make_section("Camera Models Details") for camera, params in self.stats["camera_errors"].items(): residual_grids = [ f for f in self.io_handler.ls(self.output_path) if f.startswith("residuals_" + str(camera.replace("/", "_"))) ] if not residual_grids: continue initial = params["initial_values"] optimized = params["optimized_values"] names = [""] + list(initial.keys()) rows = [] rows.append(["Initial"] + [f"{x:.4f}" for x in initial.values()]) rows.append(["Optimized"] + [f"{x:.4f}" for x in optimized.values()]) self._make_subsection(camera) self._make_table(names, rows) self.pdf.set_xy(self.margin, self.pdf.get_y() + self.margin / 2) residual_grid_height = 100 self._make_centered_image( os.path.join(self.output_path, residual_grids[0]), residual_grid_height) def make_rig_cameras_details(self) -> None: if len(self.stats["rig_errors"]) == 0: return self._make_section("Rig Cameras Details") columns_names = [ "Translation X", "Translation Y", "Translation Z", "Rotation X", "Rotation Y", "Rotation Z", ] for rig_camera_id, params in self.stats["rig_errors"].items(): initial = params["initial_values"] optimized = params["optimized_values"] rows = [] r_init, t_init = initial["rotation"], initial["translation"] r_opt, t_opt = optimized["rotation"], optimized["translation"] rows.append([ f"{t_init[0]:.4f} m", f"{t_init[1]:.4f} m", f"{t_init[2]:.4f} m", f"{r_init[0]:.4f}", f"{r_init[1]:.4f}", f"{r_init[2]:.4f}", ]) rows.append([ f"{t_opt[0]:.4f} m", f"{t_opt[1]:.4f} m", f"{t_opt[2]:.4f} m", f"{r_opt[0]:.4f}", f"{r_opt[1]:.4f}", f"{r_opt[2]:.4f}", ]) self._make_subsection(rig_camera_id) self._make_table(columns_names, rows) self.pdf.set_xy(self.margin, self.pdf.get_y() + self.margin / 2) def make_tracks_details(self) -> None: self._make_section("Tracks Details") matchgraph_height = 80 matchgraph = [ f for f in self.io_handler.ls(self.output_path) if f.startswith("matchgraph") ] self._make_centered_image( os.path.join(self.output_path, matchgraph[0]), matchgraph_height) histogram = self.stats["reconstruction_statistics"][ "histogram_track_length"] start_length, end_length = 2, 10 row_length = ["Length"] for length, _ in sorted(histogram.items(), key=lambda x: int(x[0])): if int(length) < start_length or int(length) > end_length: continue row_length.append(length) row_count = ["Count"] for length, count in sorted(histogram.items(), key=lambda x: int(x[0])): if int(length) < start_length or int(length) > end_length: continue row_count.append(f"{count}") self._make_table(None, [row_length, row_count], True) self.pdf.set_xy(self.margin, self.pdf.get_y() + self.margin) def add_page_break(self) -> None: self.pdf.add_page("P") def make_survey_data(self): self._make_section("Survey Data") self._make_centered_image( os.path.join(self.output_path, "overlap.png"), 90) self._make_centered_image( os.path.join(self.output_path, "overlap_diagram_legend.png"), 3) self.pdf.set_xy(self.margin, self.pdf.get_y() + self.margin / 2) def _add_image_label(self, text): self.pdf.set_font_size(self.small_text) self.pdf.text( self.pdf.get_x() + self.total_size / 2 - self.pdf.get_string_width(text) / 2, self.pdf.get_y() - 5, text) def make_preview(self): ortho = os.path.join(self.output_path, "ortho.png") dsm = os.path.join(self.output_path, "dsm.png") dtm = os.path.join(self.output_path, "dtm.png") count = 0 if os.path.isfile(ortho) or os.path.isfile(dsm): self._make_section("Previews") if os.path.isfile(ortho): self._make_centered_image( os.path.join(self.output_path, ortho), 110) self._add_image_label("Orthophoto") count += 1 if os.path.isfile(dsm) and self.stats.get('dsm_statistics'): self._make_centered_image(os.path.join(self.output_path, dsm), 110) self._add_image_label("Digital Surface Model") self._make_centered_image( os.path.join(self.output_path, "dsm_gradient.png"), 4) self.pdf.set_font_size(self.small_text) min_text = "{:,.2f}m".format( self.stats['dsm_statistics']['min']) max_text = "{:,.2f}m".format( self.stats['dsm_statistics']['max']) self.pdf.text(self.pdf.get_x() + 40, self.pdf.get_y() - 5, min_text) self.pdf.text( self.pdf.get_x() + 40 + 110.5 - self.pdf.get_string_width(max_text), self.pdf.get_y() - 5, max_text) count += 1 if os.path.isfile(dtm) and self.stats.get('dtm_statistics'): if count >= 2: self.add_page_break() self._make_centered_image(os.path.join(self.output_path, dtm), 110) self._add_image_label("Digital Terrain Model") self._make_centered_image( os.path.join(self.output_path, "dsm_gradient.png"), 4) self.pdf.set_font_size(self.small_text) min_text = "{:,.2f}m".format( self.stats['dtm_statistics']['min']) max_text = "{:,.2f}m".format( self.stats['dtm_statistics']['max']) self.pdf.text(self.pdf.get_x() + 40, self.pdf.get_y() - 5, min_text) self.pdf.text( self.pdf.get_x() + 40 + 110.5 - self.pdf.get_string_width(max_text), self.pdf.get_y() - 5, max_text) self.pdf.set_xy(self.margin, self.pdf.get_y() + self.margin) return True def generate_report(self) -> None: self.make_title() self.make_dataset_summary() self.make_processing_summary() self.add_page_break() if self.make_preview(): self.add_page_break() if os.path.isfile(os.path.join(self.output_path, "overlap.png")): self.make_survey_data() self.make_gps_details() if os.path.isfile( os.path.join(self.output_path, "ground_control_points.json")): self.make_gcp_error_details() self.add_page_break() self.make_features_details() self.make_reconstruction_details() self.add_page_break() self.make_tracks_details() self.make_camera_models_details()
def bill(): try: if 'username' in session and session['username'] != manager_name: if request.method == 'POST': a = datetime.datetime.now(pytz.timezone('Asia/Calcutta')) end_time = a.strftime("%c") db.details.update({"name": session['username']}, { "$set": { "destination": request.form['destination'], "end_time": end_time } }) db.scooter.update({"rider_name": session['username']}, { "$set": { "docking_station": request.form['destination'], "ignition_status": "off", "rider_name": "-" } }) details = db.docking_station.find( {"station_name": request.form['destination']}) num = details[0]["no_of_scooters"] num = num + 1 db.docking_station.update( {"station_name": request.form['destination']}, {"$set": { "no_of_scooters": num }}) data = db.details.find({"name": session['username']}) start_time = data[0]["start_time"] a = start_time.split(" ") x = db.fare.find({}) base_price = int(x[0]["base_price"]) per_min = int(x[0]["per_min"]) min_price = int(x[0]["min_price"]) global time global before_hour global before_min try: time = a[3].split(":") before_hour = int(time[0]) before_min = int(time[1]) except: time = a[4].split(":") before_hour = int(time[0]) before_min = int(time[1]) a = end_time.split(" ") global after_hour global after_min try: time = a[3].split(":") after_hour = int(time[0]) after_min = int(time[1]) except: time = a[4].split(":") after_hour = int(time[0]) after_min = int(time[1]) #calculaitng ride timming hour = after_hour - before_hour - 1 minutes = after_min + (60 - before_min) + (hour * 60) #caluclation fare on half an hour basis # quo = int(minutes/30) # rem = minutes%30 # if rem == 0: # amount = quo*50 # else: # amount = (quo + 1 )*50 amount = base_price + (minutes * per_min) balance = data[0]["balance"] balance = balance - amount db.details.update({"name": session['username']}, {"$set": { "balance": balance }}) details = { "name": session['username'], "registration_number": data[0]["registration_number"], "from": data[0]["from"], "start_time": data[0]["start_time"], "destination": data[0]["destination"], "end_time": data[0]["end_time"], "amount": amount, "duration": minutes } db.logs.insert(details) #writing into pdf file pdf = FPDF() pdf.add_page() pdf.set_font("times", 'I', size=32) pdf.cell(200, 10, txt="Ride-a-Bike", ln=1, align="C") pdf.set_font("Arial", size=20) pdf.set_line_width(1) pdf.set_draw_color(255, 0, 0) pdf.line(10, 25, 200, 25) pdf.image('static/images/logo.png', x=85, y=30, w=50) pdf.set_font("Arial", 'B', size=20) pdf.cell(200, 120, txt=session['username'], ln=1, align="L") pdf.set_font("Arial", size=18) pdf.set_line_width(1) pdf.set_draw_color(0, 0, 0) pdf.line(15, 90, 195, 90) start_station = data[0]["from"] pdf.cell(200, -75, txt="From : " + start_station + " " + start_time, ln=1, align="L") pdf.cell(200, 100, txt="To : " + request.form['destination'] + " " + end_time, ln=1, align="L") pdf.cell(200, -75, txt="Minimum Fare : " + str(base_price), ln=1, align="L") pdf.cell(200, 100, txt="Time Fare (" + str(minutes) + " mins) : " + str( (minutes * per_min)), ln=1, align="L") pdf.set_font("Arial", 'B', size=20) pdf.cell(200, -75, txt="Total : " + str(amount), ln=1, align="L") pdf.set_line_width(1) pdf.set_draw_color(0, 0, 0) pdf.line(15, 165, 195, 165) pdf.output("ride-a-bike_bill.pdf") db.details.update({"name": session['username']}, {"$set": { "status": "-" }}) return render_template("bill.html", time=end_time, data=data, minutes=minutes, amount=amount, username=session['username'], balance=balance, base_price=x[0]["base_price"], per_min=x[0]["per_min"], min_price=x[0]["min_price"]) else: return render_template("bill.html") else: return render_template("login_error.html") except: return redirect('/end_ride')
def createfunc(fname, lname, email, address, contact, portfolio, LinkedIn, profession, field, subfield, qualification, degree, skills, age, projects, AdditionalSkills, Extracurricular, Certificates, achievement, Hobbies, tagline): pdf = FPDF() pdf.add_page() pdf.set_draw_color(176, 224, 230) pdf.set_font("Arial", 'B', size=14) name = fname + " " + lname info = email + " | " + contact pdf.cell(200, 20, txt=name, ln=2, align='L') pdf.set_font("Arial", size=12) pdf.cell(200, 10, txt=info, ln=2, align='L') pdf.cell(200, 10, txt=address, ln=2, align='L') pdf.cell(200, 10, txt=portfolio, ln=2, align='L', link=portfolio) pdf.cell(200, 10, txt=LinkedIn, ln=2, align='L', link=LinkedIn) pdf.set_line_width(3) pdf.line(10, 72, 200, 72) pdf.set_line_width(0.3) pdf.set_font("Arial", 'B', size=14) pdf.cell(190, 25, txt="Objective", border='B', ln=2, align='L') pdf.set_font("Arial", size=12) pdf.multi_cell(190, 10, txt=tagline) pdf.set_font("Arial", 'B', size=14) pdf.cell(190, 10, txt="Education", border='B', ln=2, align='L') pdf.set_font("Arial", size=12) pdf.cell(200, 10, txt=field + " - " + degree, ln=2) pdf.set_font("Arial", 'B', size=14) pdf.cell(190, 10, txt="Skills", border='B', ln=2, align='L') pdf.set_font("Arial", size=12) sk = len(skills) for i in range(sk): pdf.multi_cell(200, 10, txt=chr(149) + " " + skills[i]) pdf.set_font("Arial", 'B', size=14) pdf.cell(190, 10, txt="Projects", ln=2, border='B', align='L') pdf.set_font("Arial", size=12) pdf.multi_cell(200, 10, txt=projects) pdf.set_font("Arial", 'B', size=14) pdf.cell(190, 10, txt="Additional Skills", border='B', ln=2, align='L') pdf.set_font("Arial", size=12) pdf.multi_cell(200, 10, txt=Additionalskills) pdf.set_font("Arial", 'B', size=14) pdf.cell(190, 10, txt="Extracurricular and Academic Activities", border='B', ln=2, align='L') pdf.set_font("Arial", size=12) pdf.multi_cell(200, 10, txt=Extracurricular) pdf.set_font("Arial", 'B', size=14) pdf.cell(190, 10, txt="Certifications", border='B', ln=2, align='L') pdf.set_font("Arial", size=12) pdf.multi_cell(200, 10, txt=Certificates) pdf.set_font("Arial", 'B', size=14) pdf.cell(190, 10, txt="Hobbies", border='B', ln=2, align='L') pdf.set_font("Arial", size=12) pdf.multi_cell(200, 10, txt=Hobbies) pdf.set_font("Arial", size=12) pdf.cell( 200, 20, txt= "I hereby declare that all the details given are true to the best of my knowledge and belief.", ln=2, align='L') pdf.cell(200, 25, txt=name, ln=2, align='L') pdf.output("Resume.pdf", dest='F').encode('utf-8')
from fpdf import FPDF import sys import os from Мобилки1 import in_call, out_call, sms, call_duration, k_in, k_out, k_sms from Мобилки2 import mbyte, k, internet #создание pdf = FPDF() pdf.add_page() #первая табличка pdf.set_draw_color(0, 0, 0) pdf.set_line_width(0.1) pdf.line(12, 15, 12, 44) pdf.line(12, 28, 185, 28) pdf.line(12, 32, 106, 32) pdf.line(106, 15, 106, 44) pdf.line(123, 15, 123, 44) pdf.line(61, 28, 61, 32) pdf.line(185, 15, 185, 44) pdf.line(106, 20, 123, 20) pdf.line(12, 15, 185, 15) pdf.line(12, 44, 185, 44) #текст первой таблички pdf.add_font('DejaVu', '', 'C:/Users/prova/AppData/Local/Microsoft/Windows/Fonts/DejaVuSansCondensed.ttf', uni=True) pdf.set_font("DejaVu", size = 9) pdf.cell(66, 15, txt = "АО ''Хороший банк'' г. Санкт-Петербург ", ln = 1, align = "C") pdf.cell(31, 2, txt = "Банк получателя", ln = 1, align = "C") pdf.set_font("DejaVu", size = 11) pdf.cell(200, -18, txt = "БИК", ln = 1, align = "C") pdf.set_font("DejaVu", size = 10) pdf.cell(245, 18, txt = "044547195", ln = 1, align = "C")
def MakePDFMonthCal (year, month, calParams, outputFile): startMonth = 1 endMonth = 12 # Create a calendar instance if (calParams ['FirstDay'] == SUNDAY): cal = calendar.Calendar (calendar.SUNDAY) else: cal = calendar.Calendar (calendar.MONDAY) pdfFile = FPDF (orientation=calParams['PageOrientation'], unit='in', format='letter') pdfFile.add_page() pdfFile.set_left_margin (calParams['PageXOrigin']) pdfFile.set_top_margin (calParams['PageYOrigin']) calXOffset = 0 calYOffset = 0 calMonth = month fontStyle = '' if (calParams['FontBold'] == True): fontStyle += 'b' if (calParams['FontItalic'] == True): fontStyle += 'i' calHeight = calParams['PageHeight'] calWidth = calParams['PageWidth'] numCols = 1 numRows = 1 if (month == -1): pdfFile.set_draw_color (calParams['DebugColourR'], calParams['DebugColourG'], calParams['DebugColourB']) pdfFile.set_line_width (calParams['DebugLineThickness']) pdfFile.set_xy (calParams['PageXOrigin'], calParams['PageYOrigin']) pdfFile.set_font (calParams['Font'], style=fontStyle, size=INCH_TO_POINT*calParams['YearHeader']) pdfFile.set_text_color (calParams['FontColourR'], calParams['FontColourG'], calParams['FontColourB']) pdfFile.cell (calWidth-calParams['YearGridSpacing'], calParams['YearHeader'], txt=str(year), border=calParams['Debug'], align='C') calParams['PageYOrigin'] += (calParams['YearHeader'] + calParams['YearHeaderSpace']) if (calParams['PageOrientation'] == PORTRAIT): calHeight = (calParams['PageHeight'] - calParams['YearHeader'] - calParams['YearHeaderSpace']) / YEAR_MONTH_ROWS_PORT calWidth = calParams['PageWidth'] / YEAR_MONTH_COLS_PORT numCols = YEAR_MONTH_COLS_PORT numRows = YEAR_MONTH_ROWS_PORT else: calHeight = (calParams['PageHeight'] - calParams['YearHeader'] - calParams['YearHeaderSpace']) / YEAR_MONTH_ROWS_LAND calWidth = calParams['PageWidth'] / YEAR_MONTH_COLS_LAND numCols = YEAR_MONTH_COLS_LAND numRows = YEAR_MONTH_ROWS_LAND calHeight -= calParams['YearGridSpacing'] calWidth -= calParams['YearGridSpacing'] else: startMonth = month endMonth = month for calMonth in range (startMonth, endMonth+1): if (calParams['Debug']): pdfFile.set_draw_color (calParams['DebugColourR'], calParams['DebugColourG'], calParams['DebugColourB']) pdfFile.set_line_width (calParams['DebugLineThickness']) pdfFile.rect (calParams['PageXOrigin'] + calXOffset, calParams['PageYOrigin'] + calYOffset, calWidth, calHeight) # # Make title... # pdfFile.set_text_color (calParams['FontColourR'], calParams['FontColourG'], calParams['FontColourB']) if (calParams['TitleStyle'] == 1): pdfFile.set_xy (calParams['PageXOrigin'] + calXOffset, calParams['PageYOrigin'] + calYOffset) pdfFile.set_font (calParams['Font'], style=fontStyle, size=INCH_TO_POINT*calParams['BlockMonthTitleHeight']*calHeight) pdfFile.cell (calWidth, \ calParams['BlockMonthTitleHeight']*calHeight, \ txt=calendar.month_name[calMonth], \ border=calParams['Debug'], align='C') elif (calParams['TitleStyle'] == 2): pdfFile.set_font (calParams['Font'], style=fontStyle, size=INCH_TO_POINT*calParams['BlockMonthTitleHeight']*calHeight) monthFontWidth = pdfFile.get_string_width (calendar.month_name[calMonth]) yearFontWidth = pdfFile.get_string_width (str(year)) pdfFile.set_xy (calParams['PageXOrigin'] + calXOffset, calParams['PageYOrigin'] + calYOffset) pdfFile.cell (monthFontWidth, calParams['BlockMonthTitleHeight']*calHeight + calYOffset,\ txt=calendar.month_name[calMonth], border=calParams['Debug'], align='L') pdfFile.set_xy (calParams['PageXOrigin'] + calXOffset + calWidth - yearFontWidth, calParams['PageYOrigin'] + calYOffset) pdfFile.cell (yearFontWidth, calParams['BlockMonthTitleHeight']*calHeight + calYOffset,\ txt=str(year), border=calParams['Debug'], align='R') # # Weekday titles... # dayIndices = list ( range (0, NUMBER_WEEKDAYS) ) if (calParams ['FirstDay'] == SUNDAY): dayIndices.insert (0, NUMBER_WEEKDAYS-1) dayIndices.pop () fontScaleFactor, fontMaxWidth, fontMaxSize = FindMaxFontHeight (calParams['Font'], fontStyle, \ calParams['BlockDayTitleHeight'] * calHeight, \ calWidth/NUMBER_WEEKDAYS, \ MAX_WEEKDAY_STR) for day in range (0, NUMBER_WEEKDAYS): pdfFile.set_xy (calParams['PageXOrigin'] + calXOffset + calWidth * day / NUMBER_WEEKDAYS, \ calParams['PageYOrigin'] + calYOffset + (calParams['BlockMonthTitleHeight'] + calParams['BlockTitleSpace']) * calHeight) pdfFile.set_font (calParams['Font'], style=fontStyle, size=fontScaleFactor*calParams['BlockDayTitleHeight']*calHeight) if (calParams['DayTitleStyle'] == 1): pdfFile.cell (calWidth / NUMBER_WEEKDAYS, calParams['BlockDayTitleHeight'] * calHeight, \ txt=calendar.day_name[dayIndices[day]], border=calParams['Debug'], align='C') elif (calParams['DayTitleStyle'] == 2): pdfFile.cell (calWidth / NUMBER_WEEKDAYS, calParams['BlockDayTitleHeight'] * calHeight, \ txt=calendar.day_name[dayIndices[day]], border=calParams['Debug'], align='L') # Horizontal Lines if (calParams['HorizontalLines'] == True): pdfFile.set_line_width (calParams['HorizontalLineThickness']) pdfFile.set_draw_color (calParams['HorizontalLineColourR'], calParams['HorizontalLineColourG'], calParams['HorizontalLineColourB']) HorizontalLineAmount = calParams['HorizontalLineSpacing'] * GRID_ROWS for row in range (0, HorizontalLineAmount + 1): lineXStart = calParams['PageXOrigin'] + calXOffset lineXEnd = calParams['PageXOrigin'] + calXOffset + calWidth lineYStart = calParams['PageYOrigin'] + calYOffset + calHeight \ - (row / HorizontalLineAmount) * calParams['BlockDayRegionHeight'] * calHeight lineYEnd = lineYStart pdfFile.line (lineXStart, lineYStart, lineXEnd, lineYEnd) # boxes... if (calParams['DayGridStyle'] == 4): pdfFile.set_line_width (calParams['GridLineThickness']) pdfFile.set_draw_color (calParams['DayRegionColourR'], calParams['DayRegionColourG'], calParams['DayRegionColourB']) gridOffset = calParams['DayGridSpacing'] for col in range (0, NUMBER_WEEKDAYS): for row in range (0, GRID_ROWS): boxXStart = calParams['PageXOrigin'] + calXOffset + (col / NUMBER_WEEKDAYS) * calWidth + gridOffset boxXEnd = calWidth / NUMBER_WEEKDAYS - 2*gridOffset boxYStart = calParams['PageYOrigin'] + calYOffset + (1 - calParams['BlockDayRegionHeight']) * calHeight \ + calParams['BlockDayRegionHeight'] * calHeight * row / GRID_ROWS + gridOffset boxYEnd = calParams['BlockDayRegionHeight'] * calHeight / GRID_ROWS - 2*gridOffset drawStyle = 'D' if (calParams['DayGridInvert'] == True): pdfFile.set_fill_color (calParams['FontColourR'], calParams['FontColourG'], calParams['FontColourB']) drawStyle = 'F' pdfFile.rect (boxXStart, boxYStart, boxXEnd, boxYEnd, style=drawStyle) # circles if (calParams['DayGridStyle'] == 5 or calParams['DayGridStyle'] == 6): pdfFile.set_line_width (calParams['GridLineThickness']) pdfFile.set_draw_color (calParams['DayRegionColourR'], calParams['DayRegionColourG'], calParams['DayRegionColourB']) gridOffset = calParams['DayGridSpacing'] for col in range (0, NUMBER_WEEKDAYS): for row in range (0, GRID_ROWS): boxXStart = calParams['PageXOrigin'] + calXOffset + (col / NUMBER_WEEKDAYS) * calWidth + gridOffset boxXEnd = calWidth / NUMBER_WEEKDAYS - 2*gridOffset boxYStart = calParams['PageYOrigin'] + calYOffset + (1 - calParams['BlockDayRegionHeight']) * calHeight \ + calParams['BlockDayRegionHeight'] * calHeight * row / GRID_ROWS + gridOffset boxYEnd = calParams['BlockDayRegionHeight'] * calHeight / GRID_ROWS - 2*gridOffset dX = boxXEnd dY = boxYEnd minBoxXStart = maxBoxXStart = boxXStart minBoxXEnd = maxBoxXEnd = boxXEnd minBoxYStart = maxBoxYStart = boxYStart minBoxYEnd = maxBoxYEnd = boxYEnd if (dX < dY): offset = (dY - dX) / 2 minBoxYStart += offset minBoxYEnd -= (2*offset) maxBoxXStart -= offset maxBoxXEnd += offset else: offset = (dX - dY) / 2 minBoxXStart += offset minBoxXEnd -= (2*offset) maxBoxYStart -= offset maxBoxYEnd += (2*offset) drawStyle = 'D' if (calParams['DayGridInvert'] == True): pdfFile.set_fill_color (calParams['FontColourR'], calParams['FontColourG'], calParams['FontColourB']) drawStyle = 'F' pdfFile.ellipse (minBoxXStart, minBoxYStart, minBoxXEnd, minBoxYEnd, style=drawStyle) if (calParams['DayGridStyle'] == 6): pdfFile.ellipse (boxXStart, boxYStart, boxXEnd, boxYEnd) ## ## numbers ## if (calParams['Debug']): pdfFile.set_draw_color (calParams['DebugColourR'], calParams['DebugColourG'], calParams['DebugColourB']) pdfFile.set_line_width (calParams['DebugLineThickness']) fontScaleFactor, fontMaxWidth, fontMaxSize = FindMaxFontHeight (calParams['Font'], fontStyle, \ calParams['BlockDayRegionHeight'] * calHeight / GRID_ROWS, \ calParams['DayFontScale'] * calWidth / NUMBER_WEEKDAYS, MAX_NUMBER_STR) gridOffset = 0 if (calParams['DayNumPlacement'] == 2): gridOffset += max (calParams['DayCornerOffset'] * calWidth / NUMBER_WEEKDAYS, \ calParams['DayCornerOffset'] * calParams['BlockDayRegionHeight'] * calHeight / GRID_ROWS) if (calParams['DayGridStyle'] == 4): gridOffset += calParams['DayGridSpacing'] if (calParams['DayGridInvert'] == True): pdfFile.set_text_color (255, 255, 255) else: pdfFile.set_text_color (calParams['FontColourR'], calParams['FontColourG'], calParams['FontColourB']) # iterate over all the days in the month col = 0 row = 0 for i in cal.itermonthdays (year, calMonth): # if it is a day within the month, not from the previous or next month then display it if (i != 0): # Central placement if (calParams['DayNumPlacement'] == 1): numberXLoc = calParams['PageXOrigin'] + calXOffset + col / NUMBER_WEEKDAYS * calWidth numberYLoc = calParams['PageYOrigin'] + calYOffset + (1 - calParams['BlockDayRegionHeight']) * calHeight \ + (row / GRID_ROWS) * calParams['BlockDayRegionHeight'] * calHeight \ + calParams['BlockDayRegionHeight'] * calHeight / (2 * GRID_ROWS) \ + gridOffset pdfFile.set_xy (numberXLoc, numberYLoc) pdfFile.set_font (calParams['Font'], style=fontStyle, size=fontScaleFactor*calParams['BlockDayRegionHeight'] * calHeight / GRID_ROWS) pdfFile.cell (calWidth/NUMBER_WEEKDAYS, 0, txt=str(i), align='C', border=calParams['Debug']) # Corner placement elif (calParams['DayNumPlacement'] == 2): numberXLoc = calParams['PageXOrigin'] + calXOffset + col / NUMBER_WEEKDAYS * calWidth numberYLoc = calParams['PageYOrigin'] + calYOffset + (1 - calParams['BlockDayRegionHeight']) * calHeight \ + (row / GRID_ROWS) * calParams['BlockDayRegionHeight'] * calHeight if (calParams['HorizontalLines'] == True): pdfFile.set_fill_color (255, 255, 255) pdfFile.rect (numberXLoc, numberYLoc, fontMaxWidth + 3*gridOffset, fontMaxSize / INCH_TO_POINT + 3*gridOffset, style='F') numberXLoc += gridOffset numberYLoc += gridOffset pdfFile.set_xy (numberXLoc, numberYLoc) pdfFile.set_font (calParams['Font'], style=fontStyle, size=fontScaleFactor*calParams['BlockDayRegionHeight'] * calHeight / GRID_ROWS) pdfFile.cell (fontMaxWidth, fontMaxSize / INCH_TO_POINT, txt=str(i), align='L', border=calParams['Debug']) col += 1 if (col % 7 == 0): col = 0 row += 1 if (calParams['Debug']): pdfFile.set_draw_color (calParams['DayRegionColourR'], calParams['DayRegionColourG'], calParams['DayRegionColourB']) pdfFile.set_line_width (calParams['GridLineThickness']) ## ## grid ## pdfFile.set_draw_color (calParams['DayRegionColourR'], calParams['DayRegionColourG'], calParams['DayRegionColourB']) pdfFile.set_line_width (calParams['GridLineThickness']) # horizontal grid lines if (calParams['DayGridStyle'] == 1 or calParams['DayGridStyle'] == 2): for row in range (0, GRID_ROWS+1): lineXStart = calParams['PageXOrigin'] + calXOffset lineXEnd = calParams['PageXOrigin'] + calXOffset + calWidth lineYStart = calParams['PageYOrigin'] + calYOffset + calHeight - (row / GRID_ROWS) * calParams['BlockDayRegionHeight'] * calHeight lineYEnd = lineYStart pdfFile.line (lineXStart, lineYStart, lineXEnd, lineYEnd) # vertical grid lines if (calParams['DayGridStyle'] == 1 or calParams['DayGridStyle'] == 3): for day in range (0, NUMBER_WEEKDAYS+1): lineXStart = calParams['PageXOrigin'] + calXOffset + calWidth - (day / NUMBER_WEEKDAYS) * calWidth lineXEnd = lineXStart lineYStart = calParams['PageYOrigin'] + calYOffset + (1 - calParams['BlockDayRegionHeight']) * calHeight lineYEnd = calParams['PageYOrigin'] + calYOffset + calHeight pdfFile.line (lineXStart, lineYStart, lineXEnd, lineYEnd) if (calMonth % numCols == 0): calXOffset = 0 calYOffset += (calHeight + calParams['YearGridSpacing']) else: calXOffset += calWidth calXOffset += calParams['YearGridSpacing'] pdfFile.output (outputFile) return 0
class PdfHandler: def __init__(self, orientation='P', unit='pt', format='A4'): self.pdf_handler = FPDF(orientation=orientation, unit=unit, format=format) def _set_margins(self, left, top, right=-1): return self.pdf_handler.set_margins(left, top, right=right) def _set_title(self, title): return self.pdf_handler.set_title(title) def _set_subject(self, subject): return self.pdf_handler.set_subject(subject) def _set_author(self, author): return self.pdf_handler.set_author(author) def _set_keywords(self, keywords): return self.pdf_handler.set_keywords(keywords) def _set_creator(self, creator): return self.pdf_handler.set_creator(creator) def _add_page(self, orientation=''): return self.pdf_handler.add_page(orientation=orientation) def _set_draw_color(self, r, g=-1, b=-1): return self.pdf_handler.set_draw_color(r, g=g, b=b) def _set_fill_color(self, r, g=-1, b=-1): return self.pdf_handler.set_fill_color(g=g, b=b) def _set_text_color(self, r, g=-1, b=-1): return self.pdf_handler.set_text_color(r, g=-g, b=b) def _get_string_width(self, s): return self.pdf_handler.get_string_width(s) def _set_line_width(self, width): return self.pdf_handler.set_line_width(width) def _line(self, x1, y1, x2, y2): return self.pdf_handler.line(x1, y1, x2, y2) def _dashed_line(self, x1, y1, x2, y2, dash_length=1, space_length=1): return self.pdf_handler.dashed_line(x1, y1, x2, y2, dash_length=dash_length, space_length=space_length) def _rect(self, x, y, w, h, style=''): return self.pdf_handler.rect(x, y, w, h, style=style) def _ellipse(self, x, y, w, h, style=''): return self.pdf_handler.ellipse(x, y, w, h, style=style) def _set_font(self, family, style='', size=0): return self.pdf_handler.set_font(family, style=style, size=size) def _set_font_size(self, size): return self.pdf_handler.set_font_size(size) def _text(self, x, y, txt=''): return self.pdf_handler.text(x, y, txt=txt) def _multi_cell(self, w, h, txt='', border=0, align='J', fill=0, split_only=False): return self.pdf_handler.multi_cell(w, h, txt=txt, border=border, align=align, fill=fill, split_only=split_only) def _write(self, h, txt='', link=''): return self.pdf_handler.write(h, txt=txt, link=link) def _image(self, name, x=None, y=None, w=0, h=0, image_type='', link=''): return self.pdf_handler.image(name, x=x, y=y, w=w,h=h,type=image_type,link=link) def _normalize_text(self, txt): return self.pdf_handler.normalize_text(txt) def _output(self, name='', dest=''): return self.pdf_handler.output(name, dest)
class PDFPrinter: PAGE_FORMAT = 'A4' UNIT = 'mm' MARGIN = 10 CONTENT_WIDTH = 297 - 2 * MARGIN CONTENT_HEIGHT = 210 - 2 * MARGIN HEADER_HEIGHT = 30 NOTES_HEIGHT = 17 TABLE_HEIGHT = CONTENT_HEIGHT - HEADER_HEIGHT - NOTES_HEIGHT FONT_S = 7 FONT_XS = 6.5 def __init__(self): self.colors = {} self.timelinesCount = None self.fontSize = 12 self.textColor = Color.WHITE self.pdf = FPDF(orientation='L', unit=PDFPrinter.UNIT, format=PDFPrinter.PAGE_FORMAT) self.pdf.add_font('regular', '', os.path.join('fonts', 'ubuntu', 'Ubuntu-B.ttf'), uni=True) self.pdf.add_font('condensed', '', os.path.join('fonts', 'roboto', 'RobotoCondensed-Regular.ttf'), uni=True) self.pdf.add_font('italic', '', os.path.join('fonts', 'roboto', 'RobotoCondensed-Bold.ttf'), uni=True) self.pdf.set_font("regular", size=self.fontSize) self.pdf.add_page() self.pdf.set_margins(PDFPrinter.MARGIN, PDFPrinter.MARGIN, PDFPrinter.MARGIN) self.uglyMeasure = FPDF(orientation='L', unit=PDFPrinter.UNIT, format=PDFPrinter.PAGE_FORMAT) self.uglyMeasure.add_font('regular', '', os.path.join('fonts', 'ubuntu', 'Ubuntu-B.ttf'), uni=True) self.uglyMeasure.add_font('condensed', '', os.path.join('fonts', 'roboto', 'RobotoCondensed-Regular.ttf'), uni=True) self.uglyMeasure.add_font('italic', '', os.path.join('fonts', 'roboto', 'RobotoCondensed-Bold.ttf'), uni=True) self.uglyMeasure.set_font("regular", size=self.fontSize) self.uglyMeasure.add_page() def defineColor(self, key, color): hex = color.lstrip('#') self.colors[key.lower()] = tuple( int(hex[i:i + 2], 16) for i in (0, 2, 4)) def printHeader(self, names): self.timelinesCount = len(names) boxWidth = PDFPrinter.CONTENT_WIDTH / self.timelinesCount boxPos = 0 for name in names: color = self.colors[name.lower()] x = PDFPrinter.MARGIN + boxWidth * boxPos y = PDFPrinter.MARGIN w = boxWidth h = 7 self._box(x, y, w, h, color=color, lineColor=Color.BLACK, lineWidth=0.1) self._text(x, y, w, h, text=name, color=self.textColor, font='regular', size=self.fontSize) boxPos += 1 def printTimetable(self, timetable): colCount = len(timetable.keys()) colWidth = PDFPrinter.CONTENT_WIDTH / colCount tablePositionY = 30 tableHeight = PDFPrinter.TABLE_HEIGHT + 1 tableHeaderHeight = 7 timelineRowHeight = tableHeight - tableHeaderHeight timelineRowPositionY = tablePositionY + tableHeaderHeight timeBlockWidth = (colWidth - 2) / self.timelinesCount timeWindow = self._findTimeWindow(timetable) yPerMin = (timelineRowHeight - 2) / (timeWindow["toTime"] - timeWindow["fromTime"]) colNo = 0 for key, schedules in timetable.items(): x = PDFPrinter.MARGIN + colWidth * colNo self._box(x=x, y=tablePositionY, w=colWidth, h=tableHeaderHeight, color=Color.LIGHT_GREY, lineColor=Color.BLACK, lineWidth=0.2) self._text(x=x, y=tablePositionY, w=colWidth, h=tableHeaderHeight, text=key, color=Color.BLACK, font='regular', size=self.fontSize) self._box(x=x, y=timelineRowPositionY, w=colWidth, h=timelineRowHeight, color=None, lineColor=Color.BLACK, lineWidth=0.2) self._drawTimeblocks(schedules, areaWidth=timeBlockWidth, areaPositionX=x, areaPositionY=timelineRowPositionY + 0.8, scaleY=yPerMin, timeWindowStart=timeWindow["fromTime"]) colNo += 1 def _drawTimeblocks(self, schedules, areaWidth, areaPositionX, areaPositionY, scaleY, timeWindowStart): timeBlockNo = 0 for person, timelines in schedules.items(): blockColor = self.colors[person.lower()] blockPositionX = areaPositionX + areaWidth * timeBlockNo + 0.5 * timeBlockNo + 0.5 for timeline in timelines: fromTimePosY = scaleY * (self._timeToInt(timeline.fromTime) - timeWindowStart) + 0.4 blockPositionY = areaPositionY + fromTimePosY blockHeight = scaleY * (self._timeToInt(timeline.toTime) - timeWindowStart) - fromTimePosY self._box(x=blockPositionX, y=blockPositionY, w=areaWidth, h=blockHeight, color=blockColor, lineColor=None) if (timeline.kind == "~"): self._drawLeftLines(x=blockPositionX, y=blockPositionY, w=areaWidth, h=blockHeight) elif (timeline.kind == "d"): self._drawTwoRightLines(x=blockPositionX, y=blockPositionY, w=areaWidth, h=blockHeight) elif (timeline.kind == "e"): self._drawEmptyRightCorner(x=blockPositionX, y=blockPositionY, w=areaWidth, h=blockHeight) self._drawTimeblockLabels(areaX=blockPositionX, areaY=blockPositionY, areaW=areaWidth, areaH=blockHeight, timeline=timeline, backColor=blockColor) timeBlockNo += 1 def _drawLeftLines(self, x, y, w, h): hw = h / w posX = x posXM = x + w posY = y posYM = y + h jump = 3 while (posXM > x): posX += jump posXM -= jump posY += jump * hw posYM -= jump * hw if jump == 2: jump = 0.5 else: jump = 2 if (posX < x + w and posY < y + h): self._line(posX, y, x, posY, Color.WHITE, lineWidth=0.2) if (posXM > x and posYM > y): self._line(posXM, y + h, x + w, posYM, Color.WHITE, lineWidth=0.2) def _drawTwoRightLines(self, x, y, w, h): hw = h / w posX = x posXM = x + w posY = y posYM = y + h jump = 2 for _ in range(4): posX += jump posXM -= jump posY += jump * hw posYM -= jump * hw # if jump == 2: # jump = 0.7 # else: # jump = 2 if (posX < x + w and posY < y + h): self._line(posX, y + h, x, posYM, Color.WHITE, lineWidth=0.3) if (posXM > x and posYM > y): self._line(posXM, y, x + w, posY, Color.WHITE, lineWidth=0.3) def _drawEmptyRightCorner(self, x, y, w, h): hw = h / w posX = x posXM = x + w posY = y posYM = y + h jump = 1 for p in range(9): posX += jump posXM -= jump posY += jump * hw posYM -= jump * hw if p > 5: jump = 1 else: jump = 0.2 if (posX < x + w and posY < y + h): self._line(posX, y + h, x, posYM, Color.WHITE, lineWidth=0.2) if (posXM > x and posYM > y): self._line(posXM, y, x + w, posY, Color.WHITE, lineWidth=0.2) def _drawTimeblockLabels(self, areaX, areaY, areaW, areaH, timeline, backColor): label = [] if (timeline.name): label.append(timeline.name.upper()) for component in timeline.components: label.append(component.upper()) if (label): self._textBlock(x=areaX, y=areaY, w=areaW, h=areaH, textLines=label, color=Color.WHITE, font='condensed', size=PDFPrinter.FONT_XS, align='C', backColor=backColor) if (timeline.fromTime): textSize = self._measure(text=timeline.fromTime, font='italic', size=PDFPrinter.FONT_S) self._text(x=areaX, y=areaY - 1, w=areaW / 2, h=textSize.y, text=timeline.fromTime, color=Color.WHITE, font='italic', size=PDFPrinter.FONT_S, align='L', backColor=backColor) if (timeline.toTime): textSize = self._measure(text=timeline.toTime, font='italic', size=PDFPrinter.FONT_S) self._text(x=areaX + areaW / 2, y=areaY + areaH - textSize.y + 1, w=areaW / 2, h=textSize.y, text=timeline.toTime, color=Color.WHITE, font='italic', size=PDFPrinter.FONT_S, align='R', backColor=backColor) def printNotes(self, notes): x = PDFPrinter.MARGIN y = PDFPrinter.MARGIN + PDFPrinter.HEADER_HEIGHT + PDFPrinter.TABLE_HEIGHT w = PDFPrinter.CONTENT_WIDTH h = 5 for note in notes: y += h self._text(x, y, w, 0, text=note, color=Color.BLACK, font='regular', size=self.fontSize, align='L') def save(self, fileName): self.pdf.output(fileName) def _measure(self, text, font, size): self.uglyMeasure.set_font(font, size=size) self.uglyMeasure.set_xy(0, 0) self.uglyMeasure.write(size / 2.83, txt=text) sizeX = self.uglyMeasure.get_x() self.uglyMeasure.write(size / 2.83, txt="\n") sizeY = self.uglyMeasure.get_y() result = Size(sizeX, sizeY) return result def _text(self, x, y, w, h, text, color, font, size, align='C', backColor=None): self.pdf.set_text_color(color[0], color[1], color[2]) self.pdf.set_font(font, size=size) self.pdf.set_xy(x, y) fillBackground = False if (backColor): self.pdf.set_fill_color(backColor[0], backColor[1], backColor[2]) fillBackground = True self.pdf.cell(w, h, txt=text, ln=1, align=align, fill=fillBackground) def _textBlock(self, x, y, w, h, textLines, color, font, size, align='C', backColor=None): textH = 0 texts = [] for line in textLines: textSize = self._measure(text=line, font=font, size=size) textSize.y += 1 textH += textSize.y texts.append({"txt": line, "height": textSize.y}) self.pdf.set_text_color(color[0], color[1], color[2]) self.pdf.set_font(font, size=size) posY = y + (h - textH) / 2 fillBackground = False if (backColor): self.pdf.set_fill_color(backColor[0], backColor[1], backColor[2]) fillBackground = True for line in texts: self.pdf.set_xy(x, posY) self.pdf.cell(w, line["height"], txt=line["txt"], align=align, fill=fillBackground) posY += line["height"] def _box(self, x, y, w, h, color, lineColor, lineWidth=0): style = '' if (lineColor): self.pdf.set_line_width(lineWidth) self.pdf.set_draw_color(lineColor[0], lineColor[1], lineColor[2]) style += 'D' if (color): self.pdf.set_fill_color(color[0], color[1], color[2]) style += 'F' self.pdf.rect(x, y, w, h, style) def _line(self, x1, y1, x2, y2, color, lineWidth): self.pdf.set_line_width(lineWidth) self.pdf.set_draw_color(color[0], color[1], color[2]) self.pdf.line(x1, y1, x2, y2) def _findTimeWindow(self, timetable): minTime = self._timeToInt("24:00") maxTime = self._timeToInt("00:00") for daySchedule in timetable.values(): for personalSchedule in daySchedule.values(): for schedule in personalSchedule: f = self._timeToInt(schedule.fromTime) t = self._timeToInt(schedule.toTime) minTime = min(minTime, f, t) maxTime = max(maxTime, f, t) return {"fromTime": minTime, "toTime": maxTime} def _timeToInt(self, str): parts = str.split(':') return int(parts[0]) * 60 + int(parts[1])
def process( self, output_file_name=None, dpi=150, offset=0, fps=10, height_mm=50, margins=Margin(10, 10, 10, 10), paper_format="a4", ): def draw_raster(): for ix in range(0, nx + 1): xx = x0 + ix * total.width pdf.line(xx, y0, xx, y1) if offset > 0 and ix != nx: pdf.line(xx + offset, y0, xx + offset, y1) for iy in range(0, ny + 1): yy = y0 + iy * total.height pdf.line(x0, yy, x1, yy) height_mm = float(height_mm) tmp_files = [] if self.clip: if fps != self.clip.fps: if self.verbosity > 0: print "Transcoding from {} fps to {} fps ...".format(self.clip.fps, fps) self.clip.write_videofile("tmp.mp4", fps=fps, audio=False) tmp_files.append("tmp.mp4") self.clip = VideoFileClip("tmp.mp4") self.fps = self.clip.fps self.frame_count = int(self.clip.duration * self.fps) clip_size = Size.from_tuple(self.clip.size) elif self.frames: clip_size = Size.from_tuple(self.im.size) paper = self.PAPER_SIZES[paper_format.lower()] printable_area = Size(paper.width - margins.left - margins.right, paper.height - margins.top - margins.bottom) frame_mm = Size(height_mm / clip_size.height * clip_size.width, height_mm) total = Size(offset + frame_mm.width, frame_mm.height) frame = Size(int(frame_mm.width / 25.4 * dpi), int(frame_mm.height / 25.4 * dpi)) nx = int(printable_area.width / total.width) ny = int(printable_area.height / total.height) if self.verbosity > 0: print "Input: {} fps, {}x{}, {} frames" "\n from: {}".format( self.fps, clip_size.width, clip_size.height, self.frame_count, self.input_file_name ) print "Output: {}dpi, {}x{}, {:.2f}mm x {:.2f}mm, {}x{} tiles" "\n to: {}".format( dpi, frame.width, frame.height, frame_mm.width, frame_mm.height, nx, ny, output_file_name ) pdf = FPDF(unit="mm", format=paper_format.upper(), orientation="L") pdf.set_compression(True) pdf.set_title("Funny video") pdf.set_author("Oliver Lau <*****@*****.**> - Heise Medien GmbH & Co. KG") pdf.set_creator("flippy") pdf.set_keywords("flip-book, video, animated GIF") pdf.set_draw_color(128, 128, 128) pdf.set_line_width(0.1) pdf.set_font("Helvetica", "", 12) pdf.add_page() i = 0 page = 0 tx, ty = -1, 0 x0, y0 = margins.left, margins.top x1, y1 = x0 + nx * total.width, y0 + ny * total.height if self.clip: all_frames = self.clip.iter_frames() elif self.frames: all_frames = AnimatedGif(self.im) else: all_frames = [] for f in all_frames: ready = float(i + 1) / self.frame_count if self.verbosity: sys.stdout.write("\rProcessing frames |{:30}| {}%".format("X" * int(30 * ready), int(100 * ready))) sys.stdout.flush() tx += 1 if type(f) == GifImagePlugin.GifImageFile: f.putpalette(self.palette) self.last_im.paste(f) im = self.last_im.convert("RGBA") else: im = Image.fromarray(f) im.thumbnail(frame.to_tuple()) if tx == nx: tx = 0 ty += 1 if ty == ny: ty = 0 draw_raster() pdf.add_page() page += 1 temp_file = "tmp-{}-{}-{}.jpg".format(page, tx, ty) im.save(temp_file) tmp_files.append(temp_file) x = x0 + tx * total.width y = y0 + ty * total.height pdf.image(temp_file, x=x + offset, y=y, w=frame_mm.width, h=frame_mm.height) text = Point(x, y + frame_mm.height - 2) if offset > 0: pdf.rotate(90, text.x, text.y) pdf.text(text.x, text.y + 5, "{}".format(i)) pdf.rotate(0) i += 1 if y != 0 and x != 0: draw_raster() if self.verbosity > 0: print "\nGenerating PDF ..." pdf.output(name=output_file_name) if self.verbosity > 0: print "Removing temporary files ..." for temp_file in tmp_files: os.remove(temp_file)