prawnpdf/prawn-table

Subtables ignore page breaks and bleed over margins

Closed this issue · 9 comments

I'm using the following (rather complex) code to generate a table which contains another table in a cell:

 def risk_control_matrix
    document.bounding_box(
      [
        document.bounds.left,
        document.bounds.top - 50
      ],
      width: document.bounds.width,
      height: document.bounds.height - 100
    ) do
      document.font 'Helvetica'
      document.text(
        'Risk Control Matrix',
        size: 14,
        style: :bold,
        align: :center
      )
      document.move_down(20)
      risks = [
        [
          'Risk Title',
          'Description',
          'In. Imp.',
          'In. Prob.',
          'In. Risk',
          'Control Suite',
          'Res. Imp.',
          'Res. Prob.',
          'Res. Risk'
        ]
      ]
      ratings = export.account.ratings.where(ratings_type: 'score').order('ratings_value DESC').collect{ |rating| { :name => rating.name, :colour => rating.ratings_colour } }
      assessment_rcm_risks = export.assessment.risks.active.includes(:residual_rating).where('ratings.ratings_value < 100').sort_by{ |x| x.residual_rating.ratings_value }.reverse!
      assessment_rcm_risks += export.assessment.risks.active.includes(:residual_rating).where('ratings.ratings_value = 100')
      risks += assessment_rcm_risks.map do |risk|
        controls = [
          [
            'Control Title',
            'Mitigation',
            'Design',
            'Performance',
            'Effectiveness'
          ]
        ]
        controls += risk.controls.map do |control|
          mitigation = control.mitigations.find(:first, conditions: ['risk_id = ?', risk.id])
          [
            control.title,
            mitigation.mitigation_strength,
            mitigation.design_score,
            mitigation.performance_score,
            mitigation.effectiveness_score
          ]
        end
        inner_table = document.make_table(controls, header: true, position: :center) do
          row(0).style(
            background_color: '9acd32',
            align: :center,
            text_color: 'ffffff',
            size: 7
          )
          column(0).style(
            border_widths: 0.25,
            padding: [5, 3],
            size: 7,
            width: 60
          )
          column(1).style(
            border_widths: 0.25,
            padding: [5, 3],
            size: 7,
            width: 35
          )
          column(2).style(
            border_widths: 0.25,
            padding: [5, 3],
            size: 7,
            width: 32
          )
          column(3).style(
            border_widths: 0.25,
            padding: [5, 3],
            size: 7,
            width: 45
          )
          column(4).style(
            border_widths: 0.25,
            padding: [5, 3],
            size: 7,
            width: 48
          )
          values = cells.columns(1..-1).rows(1..-1)

          low = values.filter do |cell|
            cell.content.to_s == 'Low'
          end
          low.background_color = '000000'
          low.text_color = 'ffffff'

          ineffective = values.filter do |cell|
            cell.content.to_s == 'Ineffective'
          end
          ineffective.background_color = '000000'
          ineffective.text_color = 'ffffff'

          medium = values.filter do |cell|
            cell.content.to_s == 'Medium'
          end
          medium.background_color = '9e9e9e'
          medium.text_color = 'ffffff'

          partiallyeffective = values.filter do |cell|
            cell.content.to_s == 'Partially effective'
          end
          partiallyeffective.background_color = '9e9e9e'
          partiallyeffective.text_color = 'ffffff'
        end
        if risk.controls.present?
          [
            risk.title,
            risk.description,
            risk.inherent_impact_rating.name,
            risk.inherent_probability_rating.name,
            risk.inherent_rating.name.chars.first.capitalize,
            inner_table,
            risk.residual_impact_rating.name,
            risk.residual_probability_rating.name,
            risk.residual_rating.name.chars.first.capitalize
          ]
        else
          [
            risk.title,
            risk.description,
            risk.inherent_impact_rating.name,
            risk.inherent_probability_rating.name,
            risk.inherent_rating.name.chars.first.capitalize,
            '',
            risk.residual_impact_rating.name,
            risk.residual_probability_rating.name,
            risk.residual_rating.name.chars.first.capitalize
          ]
        end
      end
      document.table(risks, header: true, position: :center, ) do
        column(0).style(
          border_widths: 0.25,
          padding: [5, 3],
          size: 8,
          width: 100
        )
        column(1).style(
          border_widths: 0.25,
          padding: [5, 3],
          size: 8,
          width: 120
        )
        column(2).style(
          border_widths: 0.25,
          padding: [5, 3],
          size: 8,
          width: 50
        )
        column(3).style(
          border_widths: 0.25,
          padding: [5, 3],
          size: 8,
          width: 45
        )
        column(4).style(
          border_widths: 0.25,
          padding: [5, 3],
          size: 8
        )
        column(5).style(
          border_widths: 0.25,
          padding: [0, 0],
          width: 220
        )
        column(6).style(
          border_widths: 0.25,
          padding: [5, 3],
          size: 8,
          width: 50
        )
        column(7).style(
          border_widths: 0.25,
          padding: [5, 3],
          size: 8,
          width: 45
        )
        column(8).style(
          border_widths: 0.25,
          padding: [5, 3],
          size: 8
        )
        row(0).style(
          background_color: '1e90ff',
          align: :center,
          padding: [5,3],
          text_color: 'ffffff',
          size: 8
        )
        ratings.each do |rating|
          values = cells.columns(1..-1).rows(1..-1)
          specific_cell = values.filter do |cell|
            cell.content.to_s == rating[:name].chars.first.capitalize || cell.content.to_s == "#{rating[:name].chars.first.capitalize} (!)"
          end
          specific_cell.background_color = 'FF0000' if rating[:colour] == 'badge-important'
          specific_cell.text_color = 'ffffff' if rating[:colour] == 'badge-important'
          specific_cell.background_color = 'FF9933' if rating[:colour] == 'badge-warning'
          specific_cell.background_color = '00CC00' if rating[:colour] == 'badge-success'
        end
      end
    end
  end

However, the table produced doesn't respect page breaks and bleeds over the margins (other tables without subtable respect page breaks fine):

subtablesissue

Sorry, should've added, this is using v0.2.1

Hi guys, is this Gem still live? Just let me know if not and I'll find another way of dealing with the subtable issue.

@betjaminrichards yes, actually @hbrandl worked on this issue extensively here: #3 but was unable to resolve it.

His latest progress is here: 7aa6f30

It's a very complex issue, I was helping him test and we went through around ~5 iterations and there were still edge case bugs before we gave up.

@johnnyshields thanks for your response. We'll check it out and see whether it fixes our issue or whether we're one of the edge cases!

That commit produces weird behaviour in my tables. It now places each individual column on a separate page (i've blocked out some of the text as it's client data).

prawtablewierdness2

Will need to investigate further when I get the time.

@betjaminrichards yeah the fix is a work in progress.

Looking at your table (and knowing how prawn works currently) I think the best option here is for you to restructure your code so as to eliminate subtables--i.e. move to a flat single parent table structure--and then limit the number of cells you're merging vertically, something like the attached image. Merged cells do not break across pages currently.

image

If you are interested to fund a proper fix to this issue please get in touch with me (i.e. a bounty), I would be potentially be willing to chip in as well.

@johnnyshields yes, would be interested, but it depends on the ball park...

And thanks for the tip on the tables. Will refactor.

Please contact me via email (johnny.shields@gmail.com). Closing this issue because it's a dupe of an existing issue.

Sorry, that it took me so long to reply.

@johnnyshields summed it up pretty nicely. I tried to fix the underlying problem / enhance the code and failed at doing so after lots of hours.

The quest you'd have to solve is quite difficult. I'm gonna add a final comment to pull request #31 indicating my problems in a minute.

In short: prawn-table can't do what you need it to do. I'm sorry about it, but it's not easily fixable.