# 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