render_on (PDF::SimpleTable)

         # File lib/pdf/simpletable.rb, line 238
    238:   def render_on(pdf)
    239:     if @column_order.empty?
    240:       raise TypeError, PDF::Writer::Lang[:simpletable_columns_undefined]
    241:     end
    242:     if @data.empty?
    243:       raise TypeError, PDF::Writer::Lang[:simpletable_data_empty]
    244:     end
    245:
    246:     low_y = descender = y0 = y1 = y = nil
    247:
    248:     @cols = PDF::Writer::OHash.new
    249:     @column_order.each do |name|
    250:       col = @columns[name]
    251:       if col
    252:         @cols[name] = col
    253:       else
    254:         @cols[name] = PDF::SimpleTable::Column.new(name)
    255:       end
    256:     end
    257:
    258:     @gap = 2 * @column_gap
    259:
    260:     max_width = __find_table_max_width__(pdf)
    261:     pos, t, x, adjustment_width, set_width = __find_table_positions__(pdf, max_width)
    262:
    263:     # if max_width is specified, and the table is too wide, and the width
    264:     # has not been set, then set the width.
    265:     if @width.zero? and @maximum_width.nonzero? and ((t - x) > @maximum_width)
    266:       @width = @maximum_width
    267:     end
    268:
    269:     if @width and (adjustment_width > 0) and (set_width < @width)
    270:         # First find the current widths of the columns involved in this
    271:         # mystery
    272:       cols0 = PDF::Writer::OHash.new
    273:       cols1 = PDF::Writer::OHash.new
    274:
    275:       xq = presentWidth = 0
    276:       last = nil
    277:
    278:       pos.each do |name, colpos|
    279:         if @cols[last].nil? or
    280:           @cols[last].width.nil? or
    281:           @cols[last].width <= 0
    282:           unless last.nil? or last.empty?
    283:             cols0[last] = colpos - xq - @gap
    284:             presentWidth += (colpos - xq - @gap)
    285:           end
    286:         else
    287:           cols1[last] = colpos - xq
    288:         end
    289:         last = name
    290:         xq = colpos
    291:       end
    292:
    293:       # cols0 contains the widths of all the columns which are not set
    294:       needed_width = @width - set_width
    295:
    296:         # If needed width is negative then add it equally to each column,
    297:         # else get more tricky.
    298:       if presentWidth < needed_width
    299:         diff = (needed_width - presentWidth) / cols0.size.to_f
    300:         cols0.each_key { |name| cols0[name] += diff }
    301:       else
    302:         cnt = 0
    303:         loop do
    304:           break if (presentWidth <= needed_width) or (cnt >= 100)
    305:           cnt += 1 # insurance policy
    306:             # Find the widest columns and the next to widest width
    307:           aWidest = []
    308:           nWidest = widest = 0
    309:           cols0.each do |name, w|
    310:             if w > widest
    311:               aWidest = [ name ]
    312:               nWidest = widest
    313:               widest = w
    314:             elsif w == widest
    315:               aWidest << name
    316:             end
    317:           end
    318:
    319:           # Then figure out what the width of the widest columns would
    320:           # have to be to take up all the slack.
    321:           newWidestWidth = widest - (presentWidth - needed_width) / aWidest.size.to_f
    322:           if newWidestWidth > nWidest
    323:             aWidest.each { |name| cols0[name] = newWidestWidth }
    324:             presentWidth = needed_width
    325:           else
    326:             # There is no space, reduce the size of the widest ones down
    327:             # to the next size down, and we will go round again
    328:             aWidest.each { |name| cols0[name] = nWidest }
    329:             presentWidth -= (widest - nWidest) * aWidest.size
    330:           end
    331:         end
    332:       end
    333:
    334:         # cols0 now contains the new widths of the constrained columns. now
    335:         # need to update the pos and max_width arrays
    336:       xq = 0
    337:       pos.each do |name, colpos|
    338:         pos[name] = xq
    339:
    340:         if @cols[name].nil? or
    341:           @cols[name].width.nil? or
    342:           @cols[name].width <= 0
    343:           if not cols0[name].nil?
    344:             xq += cols0[name] + @gap
    345:             max_width[name] = cols0[name]
    346:           end
    347:         else
    348:           xq += cols1[name] unless cols1[name].nil?
    349:         end
    350:       end
    351:
    352:       t = x + @width
    353:       pos[:__last_column__] = t
    354:     end
    355:
    356:     # now adjust the table to the correct location across the page
    357:     case @position
    358:     when :left
    359:       xref = pdf.absolute_left_margin
    360:     when :right
    361:       xref = pdf.absolute_right_margin
    362:     when :center
    363:       xref = pdf.margin_x_middle
    364:     else
    365:       xref = @position
    366:     end
    367:
    368:     case @orientation
    369:     when :left
    370:       dx = xref - t
    371:     when :right
    372:       dx = xref
    373:     when :center
    374:       dx = xref - (t / 2.0)
    375:     else
    376:       dx = xref + @orientation
    377:     end
    378:
    379:     pos.each { |k, v| pos[k] = v + dx }
    380:
    381:     base_x0 = x0 = x + dx
    382:     base_x1 = x1 = t + dx
    383:
    384:     base_left_margin = pdf.absolute_left_margin
    385:     base_pos = pos.dup
    386:
    387:       # Ok, just about ready to make me a table.
    388:     pdf.fill_color @text_color
    389:     pdf.stroke_color @shade_color
    390:
    391:     middle = (x0 + x1) / 2.0
    392:
    393:       # Start a transaction. This transaction will be used to regress the
    394:       # table if there are not enough rows protected.
    395:     tg = Transaction::Simple::Group.new(pdf, self)
    396:     tg.start_transaction(:table)
    397:     moved_once = false if @protect_rows.nonzero?
    398:
    399:     abortTable = true
    400:     loop do # while abortTable
    401:       break unless abortTable
    402:       abortTable = false
    403:
    404:       dm = pdf.absolute_left_margin - base_left_margin
    405:       base_pos.each { |k, v| pos[k] = v + dm }
    406:       x0 = base_x0 + dm
    407:       x1 = base_x1 + dm
    408:       middle = (x0 + x1) / 2.0
    409:
    410:         # If the title is set, then render it.
    411:       unless @title.nil? or @title.empty?
    412:         w = pdf.text_width(@title, @title_font_size)
    413:         _y = pdf.y - pdf.font_height(@title_font_size)
    414:         if _y < pdf.absolute_bottom_margin
    415:           pdf.start_new_page
    416:
    417:             # margins may have changed on the new page
    418:           dm = pdf.absolute_left_margin - base_left_margin
    419:           base_pos.each { |k, v| pos[k] = v + dm }
    420:           x0 = base_x0 + dm
    421:           x1 = base_x1 + dm
    422:           middle = (x0 + x1) / 2.0
    423:         end
    424:
    425:         pdf.y -= pdf.font_height(@title_font_size)
    426:         pdf.fill_color @title_color
    427:         pdf.add_text(middle - w / 2.0, pdf.y, title, @title_font_size)
    428:         pdf.y -= @title_gap
    429:       end
    430:
    431:         # Margins may have changed on the new_page.
    432:       dm = pdf.absolute_left_margin - base_left_margin
    433:       base_pos.each { |k, v| pos[k] = v + dm }
    434:       x0 = base_x0 + dm
    435:       x1 = base_x1 + dm
    436:       middle = (x0 + x1) / 2.0
    437:
    438:       y = pdf.y  # simplifies the code a bit
    439:       low_y = y if low_y.nil? or y < low_y
    440:
    441:         # Make the table
    442:       height = pdf.font_height @font_size
    443:       descender = pdf.font_descender @font_size
    444:
    445:       y0 = y + descender
    446:       dy = 0
    447:
    448:       if @show_headings
    449:         # This function will move the start of the table to a new page if
    450:         # it does not fit on this one.
    451:         hOID = __open_new_object__(pdf) if @shade_headings
    452:         pdf.fill_color @heading_color
    453:         _height, y = __table_column_headings__(pdf, pos, max_width, height,
    454:           descender, @row_gap, @heading_font_size, y)
    455:         pdf.fill_color @text_color
    456:         y0 = y + _height
    457:         y1 = y
    458:
    459:         if @shade_headings
    460:           pdf.close_object
    461:           pdf.fill_color! @shade_heading_color
    462:           pdf.rectangle(x0 - @gap / 2.0, y, x1 - x0, _height).fill
    463:           pdf.reopen_object(hOID)
    464:           pdf.close_object
    465:           pdf.restore_state
    466:         end
    467:
    468:           # Margins may have changed on the new_page
    469:         dm = pdf.absolute_left_margin - base_left_margin
    470:         base_pos.each { |k, v| pos[k] = v + dm }
    471:         x0 = base_x0 + dm
    472:         x1 = base_x1 + dm
    473:         middle = (x0 + x1) / 2.0
    474:       else
    475:         y1 = y0
    476:       end
    477:
    478:       first_line = true
    479:
    480:       # open an object here so that the text can be put in over the
    481:       # shading
    482:       tOID = __open_new_object__(pdf) unless :none == @shade_rows
    483:
    484:       cnt = 0
    485:       cnt = 1 unless @shade_headings
    486:       newPage = false
    487:       @data.each do |row|
    488:         cnt += 1
    489:           # Start a transaction that will be used for this row to prevent it
    490:           # from being split.
    491:         unless @split_rows
    492:           pageStart = pdf.pageset.size
    493:
    494:           columnStart = pdf.column_number if pdf.columns?
    495:
    496:           tg.start_transaction(:row)
    497:           row_orig = row
    498:           y_orig = y
    499:           y0_orig = y0
    500:           y1_orig = y1
    501:         end # unless @split_rows
    502:
    503:         ok = false
    504:         second_turn = false
    505:         loop do # while !abortTable and !ok
    506:           break if abortTable or ok
    507:
    508:           mx = 0
    509:           newRow = true
    510:
    511:           loop do # while !abortTable and (newPage or newRow)
    512:             break if abortTable or not (newPage or newRow)
    513:
    514:             y -= height
    515:             low_y = y if low_y.nil? or y < low_y
    516:
    517:             if newPage or y < (pdf.absolute_bottom_margin + @minimum_space)
    518:                 # check that enough rows are with the heading
    519:               moved_once = abortTable = true if @protect_rows.nonzero? and not moved_once and cnt <= @protect_rows
    520:
    521:               y2 = y - mx + (2 * height) + descender - (newRow ? 1 : 0) * height
    522:
    523:               unless :none == @show_lines
    524:                 y0 = y1 unless @show_headings
    525:
    526:                 __table_draw_lines__(pdf, pos, @gap, x0, x1, y0, y1, y2,
    527:                   @line_color, @inner_line_style, @outer_line_style,
    528:                   @show_lines)
    529:               end
    530:
    531:               unless :none == @shade_rows
    532:                 pdf.close_object
    533:                 pdf.restore_state
    534:               end
    535:
    536:               pdf.start_new_page
    537:               pdf.save_state
    538:
    539:                 # and the margins may have changed, this is due to the
    540:                 # possibility of the columns being turned on as the columns are
    541:                 # managed by manipulating the margins
    542:               dm = pdf.absolute_left_margin - base_left_margin
    543:               base_pos.each { |k, v| pos[k] = v + dm }
    544:               x0 = base_x0 + dm
    545:               x1 = base_x1 + dm
    546:
    547:               tOID = __open_new_object__(pdf) unless :none == @shade_rows
    548:
    549:               pdf.fill_color! @text_color
    550:
    551:               y = pdf.absolute_top_margin - @header_gap
    552:               low_y = y
    553:               y0 = y + descender
    554:               mx = 0
    555:
    556:               if @show_headings
    557:                 old_y = y
    558:
    559:                 pdf.fill_color @heading_color
    560:                 _height, y = __table_column_headings__(pdf, pos, max_width,
    561:                   height, descender, @row_gap, @heading_font_size, y)
    562:                 pdf.fill_color @text_color
    563:
    564:                 y0 = y + _height
    565:                 y1 = y
    566:
    567:                 if @shade_headings
    568:                   pdf.fill_color! @shade_heading_color
    569:                   pdf.rectangle(x0 - @gap / 2, y, x1 - x0, _height).fill
    570:                   pdf.fill_color @heading_color
    571:                   __table_column_headings__(pdf, pos, max_width, height,
    572:                                             descender, @row_gap,
    573:                                             @heading_font_size, old_y)
    574:                   pdf.fill_color @text_color
    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