Applied Rails: Bulleted Text With Prawn
I use the prawn gem to generate pdf documents in my Rails application. It has a drawback that it does not have built-in support for displaying bulleted text. So I wrote a simple function that took a string parameter and printed it with an indent and a leading asterisk.
But this solution had an issue if the sentences are long. The second line starts at same indent as the first line, that is, it gets aligned to the star. Users are familiar with Office documents in which the first line and second line of bulleted text start at the same indentation.
See the following output:
To fix the problem, I split the string into two, as follows:
The output looks like:
But the problem did not go away. I was told that some of the sentences could be really long, perhaps would span three or four lines. Here is such text, taken from Wikipedia, that I will use to illustrate:
This is how it looked like:
Finally, I realized that an aligned and justified bulleted text is nothing but a table without borders. We can have the asterisk in the first column and the text in the second column. Since the prawn table functions render text properly in the cells, this might work.
def bullet_item string indent 15, 0 do text "* " + string, :align => :justify end endSo you get output like:
But this solution had an issue if the sentences are long. The second line starts at same indent as the first line, that is, it gets aligned to the star. Users are familiar with Office documents in which the first line and second line of bulleted text start at the same indentation.
See the following output:
To fix the problem, I split the string into two, as follows:
def bullet_item_2 string if string.length > 110 sub_str = string[85..string.length-1] space_index = sub_str.index(' ') str1 = string[0..85+space_index] str2 = string[86+space_index..string.length-1] indent 10, 0 do text "* " + str1, :align => :justify indent(9) {text str2, :align => :justify} end else indent 10, 0 do text "* " + string, :align => :justify end end endIn here, I split the string into two, if it is more than 110 characters long. I first get the index of the space character after the 85th character and use that position to split. I then print both the strings at the same indentation.
The output looks like:
But the problem did not go away. I was told that some of the sentences could be really long, perhaps would span three or four lines. Here is such text, taken from Wikipedia, that I will use to illustrate:
A Welding Procedure Specification (WPS) is the formal written document describing welding procedures, which provides direction to the welder or welding operators for making sound and quality production welds as per the code requirements . The purpose of the document is to guide welders to the accepted procedures so that repeatable and trusted welding techniques are used. A WPS is developed for each material alloy and for each welding type used. Specific codes and/or engineering societies are often the driving force behind the development of a company's WPS. A WPS is supported by a Procedure Qualification Record (PQR or WPQR). A PQR is a record of a test weld performed and tested (more rigorously) to ensure that the procedure will produce a good weld. Individual welders are certified with a qualification test documented in a Welder Qualification Test Record (WQTR) that shows they have the understanding and demonstrated ability to work within the specified WPS.Back to the keyboard again I went, and came up with another solution, as given below:
def extractFirstPart(str) return str if str.length < 110 substr = str[85..str.length-1] space_index = substr.index(' ') if space_index != nil return str[0..85+space_index] else return str end end def bullet_item_3 string counter = 1 myStr = string.clone while myStr.length > 0 do str = extractFirstPart myStr if counter == 1 indent 10, 0 do text "* " + str, :align => :justify end else indent(19) {text str, :align => :justify} end myStr.slice! str counter += 1 end endThis time, I extracted sub-strings in a loop and printed them with indentation. The problem of any length string was solved, but not the text-alignment issue. This latest solution made the problem even worse, as lines were cut at the right end at different positions.
This is how it looked like:
Finally, I realized that an aligned and justified bulleted text is nothing but a table without borders. We can have the asterisk in the first column and the text in the second column. Since the prawn table functions render text properly in the cells, this might work.
def bullet_text_with_borderless_table data bullet_data = [] data.each {|d| bullet_data << ["*", d]} t = make_table bullet_data, {:cell_style => { :borders => [], :align => :justify, :padding => [0,10,0,0]}, header: true} indent(20) {t.draw} endAnd it worked..! Sometimes in programming life, dirty hacks are required to get clean output. Here is how my text gets displayed now: Here is --> the file that has all three solutions. The output pdf is --> here.
DZone republished this article. Please find it at:
ReplyDeletehttps://dzone.com/articles/applied-rails-bulleted-text-with-prawn