Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Applied Rails: Bulleted Text with Prawn

DZone's Guide to

Applied Rails: Bulleted Text with Prawn

Our favorite tools don't always have everything we need. In this post we take a look at using Ruby's Prawn gem in conjunction with a slight hack to attain bulleted lists.

· Web Dev Zone
Free Resource

Tips, tricks and tools for creating your own data-driven app, brought to you in partnership with Qlik.

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.

1

2

3

4

5

def bullet_item string

    indent 150 do

        text "* " + string, :align => :justify

    end

end

So you get an output like:

Image title


But this solution had an issue if the sentences were long. The second line starts at the 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:

Image title


To fix the problem, I split the string into two, as follows:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

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 100 do

            text "* " + str1, :align => :justify

            indent(9) {text str2, :align => :justify}

        end

    else

        indent 100 do

            text "* " + string, :align => :justify

        end

    end

end

In 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:

Image title

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.

So back to the keyboard again I went, and came up with another solution, as given below:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

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 100 do

                text "* " + str, :align => :justify

            end

        else

            indent(19) {text str, :align => :justify}

        end

        myStr.slice! str

        counter += 1

    end

end

This time, I extracted substrings 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:

Image title


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.


1

2

3

4

5

6

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}

end

And it worked! Sometimes in programming life, dirty hacks are required to get a clean output. Here is how my text gets displayed now:

Image title

Here is -->the file 

that has all three solutions. The output pdf is -->here

.


Explore data-driven apps with less coding and query writing, brought to you in partnership with Qlik.

Topics:
string ,gem ,text ,web dev ,ruby on rails

Published at DZone with permission of Mahboob Hussain, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}