require 'pdf_writing_tools_actions' require 'pdf_writing_tools_process' module PdfWritingTools # Disegna nel pdf (prawn), il testo rappresentato da xml_object # La proprietà .name di xml_object, deve essere uguale a 'nothtml', altrimenti, # non viene prodotto nulla. # Al momento, vengono processati i seguenti tag: # p (paragrafo) # ul (lista non ordinata) # li (elemento di lista non ordinata) # b (grassetto) # i (italico) # span # Altri tag non in elenco, vengono ignorati o causano errore # Il tag ha lo scopo di sottolineare, che questo markup language non è html, anche # se programmando, cerco di renderlo il più simile possibile all'html. # L'oggetto xml, viene letto ricorsivamente. Si crea una lista, contenente # dei dizionari. Ciascun dizionario contiene: # Il nome di un'azione: :action_name # Una lista "data", contenente un dizionario con al suo interno le specifiche # da dare a prawn, per "disegnare" del testo o per disegnare un'immagine # # Le p def self.draw_xml_object(pdf, xml_object) # Ottengo una lista di azioni, ciascuna delle quali, quando eseguita, # permette di disegnare una parte del documento xml all'interno del pdf actions_list = get_actions_list(xml_object) # "Eseguo" le azioni contenute nella lista PdfWritingToolsActions.execute_actions(pdf, actions_list, nil, []) end # Produce le actions necessarie per disegnare nel PDF l'intero documento # XML def self.get_actions_list(xml_object) actions_list = [] if xml_object.name == 'nothtml' xml_object.children.each do |child| actions_list += PdfWritingToolsProcess.process_xml_obj(child, []) end end actions_list + PdfWritingToolsActions.tr_action + PdfWritingToolsActions.tr_action + actions_list + actions_list end ######## Disegna una cella di dimensioni fissate, nella data posizione. ###### # E' Possibile indicare il colore del bordo, dello sfondo, del font e della # relativa dimensione (oltre a tutta un'altra serie di parametri (opts), vedere prima # parte della funzione) # La cella, viene disegnata "globalmente", rispetto al pdf, ossia NON relativamente # ad altri contenitori. # x e y, indicano rispettivamente le coordinate x e y del vertice in alto a # sinistra del box. # Il vertice è relativo al margine superiore del pdf, contrariamente a quanto # accade quando si utilizzano le primitive prawn (dove il margine di riferimento è # quello basso della pagina). # Bisogna pertanto prestare attenzione quando si dovessero mischiare primitive prawn # con queste funzioni. def self.draw_cell_fixed_height(pdf, x, y, w, h, t, opts={}, auto_y=false, no_background=false) t = (t.class == String ? [{text: t}] : t) font_size = opts[:font_size] || 10 style = opts[:style] || :normal align = opts[:align] || :left valign = opts[:valign] || :top font_color = opts[:font_color] || '000000' border_color = opts[:border_color] || '000000' background_color = opts[:background_color] || 'FFFFFF' left_padding = opts[:left_padding] || 5 right_padding = opts[:right_padding] || 5 top_padding = opts[:top_padding] || 5 bottom_padding = opts[:bottom_padding] || 5 pdf_height = opts[:pdf_height] || 297.mm pdf_width = opts[:pdf_width] || 21.cm result = '' y_pos1 = auto_y ? pdf.cursor : (pdf_height - y) y_pos2 = auto_y ? pdf.cursor - top_padding : (pdf_height - y - top_padding) pdf.canvas do pdf.line_width = 0.5 # Colore di sfondo della cella pdf.fill_color(background_color) # Disegna lo sfondo della cella if no_background else pdf.fill_rectangle([x, y_pos1], w, h) end pdf.stroke_color border_color pdf.stroke_rectangle([x, y_pos1], w, h) # Colore del testo nella cella pdf.fill_color(font_color) at = [x + left_padding, y_pos2] width = w - left_padding - right_padding height = h - top_padding - bottom_padding + 1.cm result = pdf.formatted_text_box(t, width: width, height: height, at: at, size: font_size, style: style, align: align, valign: valign) end result end # Disegna una cella di altezza variabile nella data posizione, in funzione del contenuto. # E' possibile indicare il colore del bordo, dello sfondo della cella e del font e della # relativa dimensione del testo ivi contenuto (oltre ad altri parametri, vedi prima parte # della funzione). # Produce un dizionario, con l'altezza della cella disegnata ed eventuale testo non disegnato # (es. nel caso di sopraggiunto fine pagina). # Il campo draw_simulation, serve ad evitare che la cella venga realmente disegnata nel pdf. # In questo modo, posso ottenere l'altezza, eventuale testo "avanzato" e prendere decisioni che # non dipendano da un'unica cella, ma da un gruppo di celle, come ad esempio quando devo # disegnare la riga di una tabella che essendo composta da più celle, ha un altezza # che dipende dall'altezza massima delle celle "autoridimensionanti" che la compongono. def self.draw_cell_auto_height(pdf, draw_simulation, x, y, w, t, opts={}, auto_y=false, no_background=false) font_size = opts[:font_size] || 10 style = opts[:style] || :normal align = opts[:align] || :left valign = opts[:valign] || :top font_color = opts[:font_color] || "000000" border_color = opts[:border_color] || "000000" background_color = opts[:background_color] || "FFFFFF" left_padding = opts[:left_padding] || 5# right_padding = opts[:right_padding] || 5# top_padding = opts[:top_padding] || 5# bottom_padding = opts[:bottom_padding] || 5# pdf_height = opts[:pdf_height] || 297.mm # pdf_width = opts[:pdf_width] || 21.cm # t = (t.class == String ? [{text: t, size: font_size, color: font_color}] : t) result = 0 y_pos1 = auto_y ? (pdf.cursor - top_padding) : (pdf_height - y - top_padding) y_pos2 = auto_y ? pdf.cursor : (pdf_height - y) pdf.canvas do # Non utilizzo l'helper (pdf.formatted_text_box), in quanto scriverebbe direttamente nel pdf # ma io ho bisogno di conoscere l'altezza del box di testo formattato, prima di scriverlo, in # modo da poter disegnare PRIMA lo sfondo b = Prawn::Text::Formatted::Box.new( t, {:at => [x + left_padding, y_pos1 ], :width => w - left_padding-right_padding, :size => font_size, :style => style, :overflow => :expand, :document => pdf, :align => align, :valign => valign}) #valign ha un comportamento da indagare, mi fa uscire il testo fuori dal box # Effettuo il render, ma in modalità "prova" (dry_run = true), così posso conoscere quale sarà # l'altezza del box prima di disegnarlo text_overflow = b.render(:dry_run => true) #text_overflow = text_overflow[:text] #### Temporaneo # Altezza del box, non ancora disegnato h = b.height if not draw_simulation # Se non sono in simulazione... # ... ora che conosco quale sarà l'altezza del box di testo, posso disegnare lo sfondo pdf.fill_color(background_color) # colore dello sfondo pdf.stroke_color(border_color) # Colore del bordo if no_background else pdf.fill_and_stroke_rectangle([x, y_pos2], (w + left_padding + right_padding), (h+top_padding+bottom_padding)) end # ... e infine, sopra lo sfondo disegno il testo pdf.fill_color(font_color) # colore del testo text_overflow = b.render() # text_overflow è l'eventuale testo avanzato end # La cella potrebbe non riuscire ad espandersi a sufficienza (troppo vicina al # fine pagine, quindi può essere che del testo "avanzi" ) result = {height: h + top_padding + bottom_padding, overflow: text_overflow} end result end def self.draw_row_fixed_height(pdf, x, y, widths, height, texts, opts={}, auto_y=false) font_sizes = opts[:font_sizes] || [10] styles = opts[:styles] || [:normal] alignments = opts[:alignments] || [:left] valignments = opts[:valignments] || [:top] font_colors = opts[:font_colors] || ["000000"] border_colors = opts[:border_colors] || ["000000"] background_colors = opts[:background_colors] || ["FFFFFF"] paddings = opts[:paddings] || [{left_padding: 5, right_padding: 5, top_padding: 5, bottom_padding: 5}] pdf_height = opts[:pdf_height] || 297.mm pdf_width = opts[:pdf_width] || 21.cm offset = 0 widths.each_with_index do |width, i| font_size = font_sizes[i] || font_sizes[0] style = styles[i] || styles[0] align = alignments[i] || alignments[0] valign = valignments[i] || valignments[0] font_color = font_colors[i] || font_colors[0] border_color = border_colors[i] || border_colors[0] background_color = background_colors[i] || background_colors[0] padding = paddings[i] || paddings[0] cell_opts = {} cell_opts[:font_size] = font_size cell_opts[:style] = style cell_opts[:align] = align cell_opts[:valign] = valign cell_opts[:font_color] = font_color cell_opts[:border_color] = border_color cell_opts[:background_color] = background_color cell_opts[:left_padding] = padding[:left_padding] cell_opts[:right_padding] = padding[:right_padding] cell_opts[:top_padding] = padding[:top_padding] cell_opts[:bottom_padding] = padding[:bottom_padding] cell_opts[:pdf_width] = 21.cm cell_opts[:pdf_height] = 297.mm draw_cell_fixed_height( pdf, x+offset, y, width, height, texts[i], cell_opts, auto_y) offset += width end pdf.y = (pdf.cursor - height) end # Produce la y su pdf, dove disegnare la prossima riga def self.draw_row_auto_height(pdf, draw_simulation, x, y, widths, texts, opts = {}, auto_y = true) font_sizes = opts[:font_sizes] || [10] styles = opts[:styles] || [:normal] alignments = opts[:alignments] || [:left] valignments = opts[:valignments] || [:top] font_colors = opts[:font_colors] || ["000000"] border_colors = opts[:border_colors] || ["000000"] background_colors = opts[:background_colors] || ["FFFFFF"] paddings = opts[:paddings] || [{left_padding: 5, right_padding: 5, top_padding: 5, bottom_padding: 5}] pdf_height = opts[:pdf_height] || 297.mm pdf_width = opts[:pdf_width] || 21.cm pdf_margin_top = opts[:pdf_margin_top] || 2.cm pdf_margin_bottom = opts[:pdf_margin_bottom] || 2.cm pdf_margin_left = opts[:pdf_margin_left] || 2.cm pdf_margin_right = opts[:pdf_margin_right] || 2.cm new_y = pdf_margin_top pdf.y = pdf.y + y loop do max_cell_height = 0 # 1 - Cerco l'altezza della riga # Per prima cosa, simulo il disegno delle varie celle che compongono la riga. # In questo modo, posso sapere quale sarà la massima altezza raggiunta da una # cella, ossia l'altezza che deve avere la riga offset = 0 widths.each_with_index do |width, i| font_size = font_sizes[i] || font_sizes[0] style = styles[i] || styles[0] align = alignments[i] || alignments[0] valign = valignments[i] || valignments[0] font_color = font_colors[i] || font_colors[0] border_color = border_colors[i] || border_colors[0] background_color = background_colors[i] || background_colors[0] padding = paddings[i] || paddings[0] cell_opts = {} cell_opts[:font_size] = font_size cell_opts[:style] = style cell_opts[:align] = align cell_opts[:valign] = valign cell_opts[:font_color] = font_color cell_opts[:border_color] = border_color cell_opts[:background_color] = background_color cell_opts[:left_padding] = padding[:left_padding] cell_opts[:right_padding] = padding[:right_padding] cell_opts[:top_padding] = padding[:top_padding] cell_opts[:bottom_padding] = padding[:bottom_padding] cell_opts[:pdf_height] = 297.mm cell_opts[:pdf_weigth] = 21.cm r = draw_cell_auto_height(pdf, draw_simulation = true, x+offset, y, width, texts[i], cell_opts, auto_y) if r[:height] > max_cell_height max_cell_height = r[:height] end offset += width end # Non voglio che la riga ecceda il margine di pagina, quindi se così fosse # ricalcolo l'altezza di riga per non superare tale margine if y + max_cell_height > pdf_height - pdf_margin_bottom max_cell_height = pdf_height - pdf_margin_bottom - y end # A seguito della simulazione, ho ottenuto l'altezza di riga... # ... ora passo al disegno vero e proprie delle celle... offset = 0 rtexts = [] widths.each_with_index do |width, i| font_size = font_sizes[i] || font_sizes[0] style = styles[i] || styles[0] align = alignments[i] || alignments[0] valign = valignments[i] || valignments[0] font_color = font_colors[i] || font_colors[0] border_color = border_colors[i] || border_colors[0] background_color = background_colors[i] || background_colors[0] padding = paddings[i] || paddings[0] cell_opts = {} cell_opts[:font_size] = font_size cell_opts[:style] = style cell_opts[:align] = align cell_opts[:valign] = valign cell_opts[:font_color] = font_color cell_opts[:border_color] = border_color cell_opts[:background_color] = background_color cell_opts[:left_padding] = padding[:left_padding] cell_opts[:right_padding] = padding[:right_padding] cell_opts[:top_padding] = padding[:top_padding] cell_opts[:bottom_padding] = padding[:bottom_padding] cell_opts[:pdf_height] = 297.mm cell_opts[:pdf_weigth] = 21.cm # ...disegno le celle vere e proprie ad altezza fissa, con l'altezza # ricavata dal passo precedente. r = draw_cell_fixed_height(pdf, x+offset, y, width, max_cell_height, texts[i], cell_opts, auto_y) # Gli eventuali "testi residui" (es. raggiunto fine pagina) li raccolgo # nel seguente array rtexts << r offset += width end texts = rtexts # Se nei testi residui, sono presenti solo stringhe vuote, posso uscire dal # loop, altrimenti, devo cambiare pagina e disegnare una nuova riga con i # testi rimanenti if !check_no_empty_string_presence(texts) new_y = y + max_cell_height break else pdf.start_new_page y = pdf_margin_top # Posiziono il cursore ad inizio della nuova pagina end end pdf.y = (pdf_height - new_y) return new_y end # Se tutte le stringhe nella lista sono "" (stringa vuota) produce false # altrimenti produce true. def self.check_no_empty_string_presence(string_list) string_list.each do |string_element| if string_element != "" and string_element != [] return true end end return false end # Dato il percorso file_path, genera un documento xml, a cui sono stati rimossi gli # a capo e gli spazi multipli def self.get_cleaned_xml_from_file(file_path) text = File.read(file_path) # Rimuove gli a capo e le tabulazioni sostituendoli con uno spazio text = text.gsub(/\n|\t/, ' ') # Rimuove le spaziature multiple sostituendole con uno spazio text = text.gsub(/\s+/, ' ') Nokogiri::XML(text) end end