render_on (PDF::SimpleTable)

         # File lib/pdf/simpletable.rb, line 239
    239:   def render_on(pdf)
    240:     if @column_order.empty?
    241:       raise TypeError, PDF::Writer::Lang[:simpletable_columns_undefined]
    242:     end
    243:     if @data.empty?
    244:       raise TypeError, PDF::Writer::Lang[:simpletable_data_empty]
    245:     end
    246:
    247:     low_y = descender = y0 = y1 = y = nil
    248:
    249:     @cols = PDF::Writer::OHash.new
    250:     @column_order.each do |name|
    251:       col = @columns[name]
    252:       if col
    253:         @cols[name] = col
    254:       else
    255:         @cols[name] = PDF::SimpleTable::Column.new(name)
    256:       end
    257:     end
    258:
    259:     @gap = 2 * @column_gap
    260:
    261:     max_width = __find_table_max_width__(pdf)
    262:     pos, t, x, adjustment_width, set_width = __find_table_positions__(pdf, max_width)
    263:
    264:     # if max_width is specified, and the table is too wide, and the width
    265:     # has not been set, then set the width.
    266:     if @width.zero? and @maximum_width.nonzero? and ((t - x) > @maximum_width)
    267:       @width = @maximum_width
    268:     end
    269:
    270:     if @width and (adjustment_width > 0) and (set_width < @width)
    271:         # First find the current widths of the columns involved in this
    272:         # mystery
    273:       cols0 = PDF::Writer::OHash.new
    274:       cols1 = PDF::Writer::OHash.new
    275:
    276:       xq = presentWidth = 0
    277:       last = nil
    278:
    279:       pos.each do |name, colpos|
    280:         if @cols[last].nil? or
    281:           @cols[last].width.nil? or
    282:           @cols[last].width <= 0
    283:           unless last.nil? or last.empty?
    284:             cols0[last] = colpos - xq - @gap
    285:             presentWidth += (colpos - xq - @gap)
    286:           end
    287:         else
    288:           cols1[last] = colpos - xq
    289:         end
    290:         last = name
    291:         xq = colpos
    292:       end
    293:
    294:       # cols0 contains the widths of all the columns which are not set
    295:       needed_width = @width - set_width
    296:
    297:         # If needed width is negative then add it equally to each column,
    298:         # else get more tricky.
    299:       if presentWidth < needed_width
    300:         diff = (needed_width - presentWidth) / cols0.size.to_f
    301:         cols0.each_key { |name| cols0[name] += diff }
    302:       else
    303:         cnt = 0
    304:         loop do
    305:           break if (presentWidth <= needed_width) or (cnt >= 100)
    306:           cnt += 1 # insurance policy
    307:             # Find the widest columns and the next to widest width
    308:           aWidest = []
    309:           nWidest = widest = 0
    310:           cols0.each do |name, w|
    311:             if w > widest
    312:               aWidest = [ name ]
    313:               nWidest = widest
    314:               widest = w
    315:             elsif w == widest
    316:               aWidest << name
    317:             end
    318:           end
    319:
    320:           # Then figure out what the width of the widest columns would
    321:           # have to be to take up all the slack.
    322:           newWidestWidth = widest - (presentWidth - needed_width) / aWidest.size.to_f
    323:           if newWidestWidth > nWidest
    324:             aWidest.each { |name| cols0[name] = newWidestWidth }
    325:             presentWidth = needed_width
    326:           else
    327:             # There is no space, reduce the size of the widest ones down
    328:             # to the next size down, and we will go round again
    329:             aWidest.each { |name| cols0[name] = nWidest }
    330:             presentWidth -= (widest - nWidest) * aWidest.size
    331:           end
    332:         end
    333:       end
    334:
    335:         # cols0 now contains the new widths of the constrained columns. now
    336:         # need to update the pos and max_width arrays
    337:       xq = 0
    338:       pos.each do |name, colpos|
    339:         pos[name] = xq
    340:
    341:         if @cols[name].nil? or
    342:           @cols[name].width.nil? or
    343:           @cols[name].width <= 0
    344:           if not cols0[name].nil?
    345:             xq += cols0[name] + @gap
    346:             max_width[name] = cols0[name]
    347:           end
    348:         else
    349:           xq += cols1[name] unless cols1[name].nil?
    350:         end
    351:       end
    352:
    353:       t = x + @width
    354:       pos[:__last_column__] = t
    355:     end
    356:
    357:     # now adjust the table to the correct location across the page
    358:     case @position
    359:     when :left
    360:       xref = pdf.absolute_left_margin
    361:     when :right
    362:       xref = pdf.absolute_right_margin
    363:     when :center
    364:       xref = pdf.margin_x_middle
    365:     else
    366:       xref = @position
    367:     end
    368:
    369:     case @orientation
    370:     when :left
    371:       dx = xref - t
    372:     when :right
    373:       dx = xref
    374:     when :center
    375:       dx = xref - (t / 2.0)
    376:     else
    377:       dx = xref + @orientation
    378:     end
    379:
    380:     pos.each { |k, v| pos[k] = v + dx }
    381:
    382:     base_x0 = x0 = x + dx
    383:     base_x1 = x1 = t + dx
    384:
    385:     base_left_margin = pdf.absolute_left_margin
    386:     base_pos = pos.dup
    387:
    388:       # Ok, just about ready to make me a table.
    389:     pdf.fill_color @text_color
    390:     pdf.stroke_color @shade_color
    391:
    392:     middle = (x0 + x1) / 2.0
    393:
    394:       # Start a transaction. This transaction will be used to regress the
    395:       # table if there are not enough rows protected.
    396:     tg = Transaction::Simple::Group.new(pdf, self)
    397:     tg.start_transaction(:table)
    398:     moved_once = false if @protect_rows.nonzero?
    399:
    400:     abortTable = true
    401:     loop do # while abortTable
    402:       break unless abortTable
    403:       abortTable = false
    404:
    405:       dm = pdf.absolute_left_margin - base_left_margin
    406:       base_pos.each { |k, v| pos[k] = v + dm }
    407:       x0 = base_x0 + dm
    408:       x1 = base_x1 + dm
    409:       middle = (x0 + x1) / 2.0
    410:
    411:         # If the title is set, then render it.
    412:       unless @title.nil? or @title.empty?
    413:         w = pdf.text_width(@title, @title_font_size)
    414:         _y = pdf.y - pdf.font_height(@title_font_size)
    415:         if _y < pdf.absolute_bottom_margin
    416:           pdf.start_new_page
    417:
    418:             # margins may have changed on the new page
    419:           dm = pdf.absolute_left_margin - base_left_margin
    420:           base_pos.each { |k, v| pos[k] = v + dm }
    421:           x0 = base_x0 + dm
    422:           x1 = base_x1 + dm
    423:           middle = (x0 + x1) / 2.0
    424:         end
    425:
    426:         pdf.y -= pdf.font_height(@title_font_size)
    427:         pdf.fill_color @title_color
    428:         pdf.add_text(middle - w / 2.0, pdf.y, title, @title_font_size)
    429:         pdf.y -= @title_gap
    430:       end
    431:
    432:         # Margins may have changed on the new_page.
    433:       dm = pdf.absolute_left_margin - base_left_margin
    434:       base_pos.each { |k, v| pos[k] = v + dm }
    435:       x0 = base_x0 + dm
    436:       x1 = base_x1 + dm
    437:       middle = (x0 + x1) / 2.0
    438:
    439:       y = pdf.y  # simplifies the code a bit
    440:       low_y = y if low_y.nil? or y < low_y
    441:
    442:         # Make the table
    443:       height = pdf.font_height @font_size
    444:       descender = pdf.font_descender @font_size
    445:
    446:       y0 = y + descender
    447:       dy = 0
    448:
    449:       if @show_headings
    450:         # This function will move the start of the table to a new page if
    451:         # it does not fit on this one.
    452:         hOID = __open_new_object__(pdf) if @shade_headings
    453:         pdf.fill_color @heading_color
    454:         _height, y = __table_column_headings__(pdf, pos, max_width, height,
    455:           descender, @row_gap, @heading_font_size, y)
    456:         pdf.fill_color @text_color
    457:         y0 = y + _height
    458:         y1 = y
    459:
    460:         if @shade_headings
    461:           pdf.close_object
    462:           pdf.fill_color! @shade_heading_color
    463:           pdf.rectangle(x0 - @gap / 2.0, y, x1 - x0, _height).fill
    464:           pdf.reopen_object(hOID)
    465:           pdf.close_object
    466:           pdf.restore_state
    467:         end
    468:
    469:           # Margins may have changed on the new_page
    470:         dm = pdf.absolute_left_margin - base_left_margin
    471:         base_pos.each { |k, v| pos[k] = v + dm }
    472:         x0 = base_x0 + dm
    473:         x1 = base_x1 + dm
    474:         middle = (x0 + x1) / 2.0
    475:       else
    476:         y1 = y0
    477:       end
    478:
    479:       first_line = true
    480:
    481:       # open an object here so that the text can be put in over the
    482:       # shading
    483:       tOID = __open_new_object__(pdf) unless :none == @shade_rows
    484:
    485:       cnt = 0
    486:       cnt = 1 unless @shade_headings
    487:       newPage = false
    488:       @data.each do |row|
    489:         cnt += 1
    490:           # Start a transaction that will be used for this row to prevent it
    491:           # from being split.
    492:         unless @split_rows
    493:           pageStart = pdf.pageset.size
    494:
    495:           columnStart = pdf.column_number if pdf.columns?
    496:
    497:           tg.start_transaction(:row)
    498:           row_orig = row
    499:           y_orig = y
    500:           y0_orig = y0
    501:           y1_orig = y1
    502:         end # unless @split_rows
    503:
    504:         ok = false
    505:         second_turn = false
    506:         loop do # while !abortTable and !ok
    507:           break if abortTable or ok
    508:
    509:           mx = 0
    510:           newRow = true
    511:
    512:           loop do # while !abortTable and (newPage or newRow)
    513:             break if abortTable or not (newPage or newRow)
    514:
    515:             y -= height
    516:             low_y = y if low_y.nil? or y < low_y
    517:
    518:             if newPage or y < (pdf.absolute_bottom_margin + @minimum_space)
    519:                 # check that enough rows are with the heading
    520:               moved_once = abortTable = true if @protect_rows.nonzero? and not moved_once and cnt <= @protect_rows
    521:
    522:               y2 = y - mx + (2 * height) + descender - (newRow ? 1 : 0) * height
    523:
    524:               unless :none == @show_lines
    525:                 y0 = y1 unless @show_headings
    526:
    527:                 __table_draw_lines__(pdf, pos, @gap, x0, x1, y0, y1, y2,
    528:                   @line_color, @inner_line_style, @outer_line_style,
    529:                   @show_lines)
    530:               end
    531:
    532:               unless :none == @shade_rows
    533:                 pdf.close_object
    534:                 pdf.restore_state
    535:               end
    536:
    537:               pdf.start_new_page
    538:               pdf.save_state
    539:
    540:                 # and the margins may have changed, this is due to the
    541:                 # possibility of the columns being turned on as the columns are
    542:                 # managed by manipulating the margins
    543:               dm = pdf.absolute_left_margin - base_left_margin
    544:               base_pos.each { |k, v| pos[k] = v + dm }
    545:               x0 = base_x0 + dm
    546:               x1 = base_x1 + dm
    547:
    548:               tOID = __open_new_object__(pdf) unless :none == @shade_rows
    549:
    550:               pdf.fill_color! @text_color
    551:
    552:               y = pdf.absolute_top_margin - @header_gap
    553:               low_y = y
    554:               y0 = y + descender
    555:               mx = 0
    556:
    557:               if @show_headings
    558:                 hOID = __open_new_object__(pdf) if @shade_headings
    559:
    560:                 pdf.fill_color @heading_color
    561:                 _height, y = __table_column_headings__(pdf, pos, max_width,
    562:                   height, descender, @row_gap, @heading_font_size, y)
    563:                 pdf.fill_color @text_color
    564:
    565:                 y0 = y + _height
    566:                 y1 = y
    567:
    568:                 if @shade_headings
    569:                   pdf.close_object
    570:                   pdf.fill_color! @shade_heading_color
    571:                   pdf.rectangle(x0 - @gap / 2, y, x1 - x0, _height).fill
    572:                   pdf.reopen_object(hOID)
    573:                   pdf.close_object
    574:                   pdf.restore_state
    575:                 end
    576:
    577:                 dm = pdf.absolute_left_margin - base_left_margin
    578:                 base_pos.each { |k, v| pos[k] = v + dm }
    579:                 x0 = base_x0 + dm
    580:                 x1 = base_x1 + dm
    581:                 middle = (x0 + x1) / 2.0
    582:               else
    583:                 y1 = y0
    584:               end
    585:
    586:               first_line = true
    587:               y -= height
    588:               low_y = y if low_y.nil? or y < low_y
    589:             end
    590:
    591:             newRow = false
    592:
    593:               # Write the actual data. If these cells need to be split over
    594:               # a page, then newPage will be set, and the remaining text
    595:               # will be placed in leftOvers
    596:             newPage = false
    597:             leftOvers = PDF::Writer::OHash.new
    598:
    599:             @cols.each do |name, column|
    600:               pdf.pointer = y + height
    601:               colNewPage = false
    602:
    603:               unless row[name].nil?
    604:                 lines = row[name].to_s.split(/\n/)
    605:                 if column and column.link_name
    606:                   lines.map! do |kk|
    607:                     link = row[column.link_name]
    608:                     if link
    609:                       "<c:alink uri='#{link}'>#{kk}</c:alink>"
    610:                     else
    611:                       kk
    612:                     end
    613:                   end
    614:                 end
    615:               else
    616:                 lines = []
    617:               end
    618:
    619:               pdf.y -= @row_gap
    620:
    621:               lines.each do |line|
    622:                 pdf.send(:preprocess_text, line)
    623:                 start = true
    624:
    625:                 loop do
    626:                   break if (line.nil? or line.empty?) and not start
    627:                   start = false
    628:
    629:                   _y = pdf.y - height if not colNewPage
    630:
    631:                     # a new page is required
    632:                   newPage = colNewPage = true if _y < pdf.absolute_bottom_margin
    633:
    634:                   if colNewPage
    635:                     if leftOvers[name].nil?
    636:                       leftOvers[name] = [line]
    637:                     else
    638:                       leftOvers[name] << "\n#{line}"
    639:                     end
    640:                     line = nil
    641:                   else
    642:                     if column and column.justification
    643:                       just = column.justification
    644:                     end
    645:                     just ||= :left
    646:
    647:                     pdf.y = _y
    648:                     line = pdf.add_text_wrap(pos[name], pdf.y,
    649:                                              max_width[name], line,
    650:                                              @font_size, just)
    651:                   end
    652:                 end
    653:               end
    654:
    655:               dy = y + height - pdf.y + @row_gap
    656:               mx = dy - height * (newPage ? 1 : 0) if (dy - height * (newPage ? 1 : 0)) > mx
    657:             end
    658:
    659:               # Set row to leftOvers so that they will be processed onto the
    660:               # new page
    661:             row = leftOvers
    662:
    663:             # Now add the shading underneath
    664:             unless :none == @shade_rows
    665:               pdf.close_object
    666:
    667:               if (cnt % 2).zero?
    668:                 pdf.fill_color!(@shade_color)
    669:                 pdf.rectangle(x0 - @gap / 2.0, y + descender + height - mx, x1 - x0, mx).fill
    670:               elsif (cnt % 2).nonzero? and :striped == @shade_rows
    671:                 pdf.fill_color!(@shade_color2)
    672:                 pdf.rectangle(x0 - @gap / 2.0, y + descender + height - mx, x1 - x0, mx).fill
    673:               end
    674:               pdf.reopen_object(tOID)
    675:             end
    676:
    677:             if :inner == @show_lines or :all == @show_lines
    678:               # draw a line on the top of the block
    679:               pdf.save_state
    680:               pdf.stroke_color! @line_color
    681:               if first_line
    682:                 pdf.stroke_style @outer_line_style
    683:                 first_line = false
    684:               else
    685:                 pdf.stroke_style @inner_line_style
    686:               end
    687:               pdf.line(x0 - @gap / 2.0, y + descender + height, x1 - @gap / 2.0, y + descender + height).stroke
    688:               pdf.restore_state
    689:             end
    690:           end
    691:
    692:           y = y - mx + height
    693:           pdf.y = y
    694:           low_y = y if low_y.nil? or y < low_y
    695:
    696:             # checking row split over pages
    697:           unless @split_rows
    698:             if (((pdf.pageset.size != pageStart) or (pdf.columns? and columnStart != pdf.column_number)) and not second_turn)
    699:               # then we need to go back and try that again!
    700:               newPage = second_turn = true
    701:               tg.rewind_transaction(:row)
    702:               row = row_orig
    703:               low_y = y = y_orig
    704:               y0 = y0_orig
    705:               y1 = y1_orig
    706:               ok = false
    707:
    708:               dm = pdf.absolute_left_margin - base_left_margin
    709:               base_pos.each { |k, v| pos[k] = v + dm }
    710:               x0 = base_x0 + dm
    711:               x1 = base_x1 + dm
    712:             else
    713:               tg.commit_transaction(:row)
    714:               ok = true
    715:             end
    716:           else
    717:             ok = true # don't go 'round the loop if splitting rows is allowed
    718:           end
    719:         end
    720:
    721:         if abortTable
    722:             # abort_transaction if not ok only the outer transaction should
    723:             # be operational.
    724:           tg.rewind_transaction(:table)
    725:           pdf.start_new_page
    726:             # fix a bug where a moved table will take up the whole page.
    727:           low_y = nil
    728:           pdf.save_state
    729:           break
    730:         end
    731:       end
    732:     end
    733:
    734:     if low_y <= y
    735:       y2 = low_y + descender
    736:     else
    737:       y2 = y + descender
    738:     end
    739:
    740:     unless :none == @show_lines
    741:       y0 = y1 unless @show_headings
    742:
    743:       __table_draw_lines__(pdf, pos, @gap, x0, x1, y0, y1, y2, @line_color,
    744:         @inner_line_style, @outer_line_style, @show_lines)
    745:     end
    746:
    747:     # close the object for drawing the text on top
    748:     unless :none == @shade_rows
    749:       pdf.close_object
    750:       pdf.restore_state
    751:     end
    752:
    753:     pdf.y = low_y
    754:
    755:       # Table has been put on the page, the rows guarded as required; commit.
    756:     tg.commit_transaction(:table)
    757:
    758:     y
    759:   rescue Exception => ex
    760:     begin
    761:       tg.abort_transaction(:table) if tg.transaction_open?
    762:     rescue
    763:       nil
    764:     end
    765:     raise ex
    766:   end