File:  [LON-CAPA] / loncom / xml / lontable.pm
Revision 1.15: download - view: text, annotated - select for diffs
Thu Nov 18 17:12:14 2010 UTC (13 years, 5 months ago) by raeburn
Branches: MAIN
CVS tags: version_2_9_X, version_2_9_1, version_2_10_0_RC2, version_2_10_0, HEAD
- prevent divide-by-zero error
- duplicate declaration for $cw

    1: # The LearningOnline Network with CAPA
    2: #  Generating TeX tables.
    3: #
    4: # $Id: lontable.pm,v 1.15 2010/11/18 17:12:14 raeburn Exp $
    5: # 
    6: #
    7: # Copyright Michigan State University Board of Trustees
    8: #
    9: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
   10: #
   11: # LON-CAPA is free software; you can redistribute it and/or modify
   12: # it under the terms of the GNU General Public License as published by
   13: # the Free Software Foundation; either version 2 of the License, or
   14: # (at your option) any later version.
   15: #
   16: # LON-CAPA is distributed in the hope that it will be useful,
   17: # but WITHOUT ANY WARRANTY; without even the implied warranty of
   18: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   19: # GNU General Public License for more details.
   20: #
   21: # You should have received a copy of the GNU General Public License
   22: # along with LON-CAPA; if not, write to the Free Software
   23: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   24: #
   25: # /home/httpd/html/adm/gpl.txt
   26: #
   27: # http://www.lon-capa.org/
   28: ## Copyright for TtHfunc and TtMfunc by Ian Hutchinson. 
   29: # TtHfunc and TtMfunc (the "Code") may be compiled and linked into 
   30: # binary executable programs or libraries distributed by the 
   31: # Michigan State University (the "Licensee"), but any binaries so 
   32: # distributed are hereby licensed only for use in the context
   33: # of a program or computational system for which the Licensee is the 
   34: # primary author or distributor, and which performs substantial 
   35: # additional tasks beyond the translation of (La)TeX into HTML.
   36: # The C source of the Code may not be distributed by the Licensee
   37: # to any other parties under any circumstances.
   38: #
   39: 
   40: # This module is a support packkage that helps londefdef generate
   41: # LaTeX tables using the Apache::lonlatextable package.  A prerequisite is that
   42: # the print generator must have added the following to the LaTeX 
   43: #
   44: #  \usepackage{xtab}
   45: #  \usepackage{booktabs}
   46: #  \usepackage{array}
   47: #  \usepackage{colortbl}
   48: #  \usepackage{xcolor}
   49: #
   50: #  These packages are installed in the packaged LaTeX distributions we know of as of
   51: #  11/24/2008
   52: #
   53: 
   54: 
   55: 
   56: package Apache::lontable;
   57: use strict;
   58: use Apache::lonlatextable;
   59: use Apache::lonnet;		# for trace logging.
   60: 
   61: my $tracing = 0;		# Set to 1 to enable log tracing. 2 for local sub tracing.
   62: 
   63: =pod
   64: 
   65: =head1  lontable Table generation assistant for the LaTeX target
   66: 
   67: This module contains support software for generating tables in LaTeX output mode 
   68: In this implementation, we use the Apache::lonlatextable package to do the actual final formatting.
   69: Each table creates a new object.  Table objects can have global properties configured.
   70: The main operations on a table object are:
   71: 
   72: =over 3
   73: 
   74: =item start_row  
   75: 
   76: Opens a new table row.
   77: 
   78: =item end_row
   79: 
   80: Closes a table row.
   81: 
   82: =item configure_row
   83: 
   84: Modifies a configuration item in the currently open row.
   85: 
   86: =item generate
   87: 
   88: Returns the generated table string.
   89: 
   90: =item configure
   91: 
   92: Configures a table's global configuration.
   93: 
   94: =item add_cell
   95: 
   96: Add and configure a cell to the current row.6
   97: 
   98: =back
   99: 
  100: =cut
  101: 
  102: =pod
  103: 
  104: =head2 new - create a new object.
  105: 
  106: Create a new table object.  Any of the raw table configuration items can be
  107: modified by this.  These configuration items include:
  108: 
  109:   my $table = lontable::new(\%config_hash)
  110: 
  111: =over3
  112: 
  113: 
  114: =item alignment
  115: 
  116: Table alignment.  Some table styles support this but not all.
  117: 
  118: =item tableborder
  119: 
  120: If true, a border is drawn around the table.
  121: 
  122: =item cellborder
  123: 
  124: If true, borders are drawn around the cells inside a table.
  125: 
  126: =item caption
  127: 
  128: The table caption text.
  129: 
  130: =item theme
  131: 
  132: The theme of the table to use.  Defaults to Zurich.  Themes we know about are:
  133: NYC, NYC2, Zurich, Berlin, Dresden, Houston, Miami, plain, Paris.  Other themes can be added
  134: to the Apache::lonlatextable package, and they will become supported automatically, as theme names are
  135: not error checked.  Any use of a non-existent theme is reported by the Apache::lonlatextable package
  136: when the table text is generated.
  137: 
  138: =item width
  139: 
  140: The width of the table.   in any
  141: TeX unit measure e.g.  10.8cm  This forces the table to the
  142: tabularx environment.  It also forces the declarations for
  143: cells to be paragraph mode which supports more internal formatting.
  144: 
  145: =back
  146: 
  147: =head3 Member data
  148: 
  149: The object hash has the following members:
  150: 
  151: =over 3
  152: 
  153: =item column_count 
  154: 
  155: Maintained internally, the number of colums in the widest row.
  156: 
  157: =item alignment
  158: 
  159: Table alignment (configurable) "left", "center", or "right".
  160: 
  161: =item outer_border
  162: 
  163: True if a border should be drawn around the entire table (configurable)
  164: 
  165: =item inner_borders
  166: 
  167: True if a border should be drawn around all cells (configurable).
  168: 
  169: =item caption
  170: 
  171: Table caption (configurable).
  172: 
  173: =item theme
  174: 
  175: Theme desired (configurable).
  176: 
  177: =item width
  178: 
  179: If defined, the width of the table (should be supplied
  180: in fraction of column width e.g. .75 for 75%.
  181: 
  182: =item row_open 
  183: 
  184: True if a row is open and not yet closed.
  185: 
  186: =item rows
  187: 
  188: Array of row data. This is an array of hashes described below.
  189: 
  190: =back
  191: 
  192: =head3 Row data.
  193: 
  194: Each row of table data is an element of the rows hash array.  Hash elements are
  195: 
  196: =over 3
  197: 
  198: 
  199: =item default_halign 
  200: 0
  201: Default horizontal alignment for cells in this row.
  202: 
  203: =item default_valign
  204: 
  205: Default vertical alignment for cells in this row (may be ignored).
  206: 
  207: =item cell_width
  208:  
  209: The width of the row in cells.  This is the sum of the column spans 
  210: of the cells in the row.
  211: 
  212: =item cells
  213: 
  214: Array of hashes where each element represents the data for a cell.
  215: The contents of each element of this hash are described below:
  216: 
  217: =over 3
  218: 
  219: =item header
  220: 
  221: If present, the row is a 'header' that is it was made via the
  222: <th> tag.
  223: 
  224: =item halign
  225: 
  226: If present, overrides the row default horizontal alignment.
  227: 
  228: =item valign
  229: 
  230: if present, override the row default vertical alignment.
  231: 
  232: =item rowspan
  233: 
  234: If present, indicates the number of rows this cell spans.
  235: 
  236: =item colspan
  237: 
  238: If present indicates the number of columns this cell spans.
  239: Note that a cell can span both rows and columns.
  240: 
  241: =item start_col
  242: 
  243: The starting column of the cell in the table grid.
  244: 
  245: =item contents
  246: 
  247: The contents of the cell.
  248: 
  249: =back
  250: 
  251: =back
  252: 
  253: =cut
  254: 
  255: sub new {
  256:     my ($class, $configuration) = @_;
  257: 
  258: 
  259:     #  Initialize the object member data with the default values
  260:     #  then override with any stuff in $configuration.
  261: 
  262:     my $self = {
  263: 	alignment      => "left",
  264: 	outer_border   => 0,
  265: 	inner_border  => 0,
  266: 	caption        => "",
  267: 	theme          => "plain",
  268: 	column_count   => 0,
  269: 	row_open       => 0,
  270: 	rows           => [],
  271: 	col_widths      => {}
  272:     };
  273: 
  274:     foreach my $key (keys %$configuration) {
  275: 	$self->{$key} = $$configuration{$key};
  276:     }
  277: 
  278:     bless($self, $class);
  279: 
  280:     return $self;
  281: }
  282: 
  283: 
  284: #-------------------------------------------------------------------------
  285: #
  286: #  Methods that get/set table global configuration.
  287: #
  288: 
  289: =pod
  290: 
  291: =head2 Gets/set alignment.  
  292: 
  293: If the method is passed a new alignment value, that replaces the current one.
  294: Regardless, the current alignment is used:
  295: 
  296: =head3 Examples:
  297: 
  298:  my $align = $table->alignment(); # Return current alignment
  299:  $table->alignment("center");     # Attempt centered alignment.
  300: 
  301: =cut
  302: 
  303: sub alignment {
  304:     my ($self, $new_value) = @_;
  305: 
  306:     if ($tracing) {&Apache::lonnet::logthis("alignment = $new_value");}
  307: 
  308:     if (defined($new_value)) {
  309: 	$self->{'alignment'} = $new_value;
  310:     }
  311:     return $self->{'alignment'};
  312: }
  313: 
  314: =pod
  315: 
  316: =head2 table_border
  317: 
  318: Set or get the presence of an outer border in the table.
  319: If passed a parameter, that parameter replaces the current request
  320: for or not for an outer border. Regardless, the function returns
  321: the final value of the outer_border request.
  322: 
  323: =head3 Examples:
  324: 
  325:   $table->table_border(1);      # Request an outer border.
  326:   my $outer_requested = $table->table_border();
  327: 
  328: =cut
  329: 
  330: sub table_border {
  331:     my ($self, $new_value) = @_;
  332: 
  333:     if ($tracing) {&Apache::lonnet::logthis("table_border $new_value");}
  334: 
  335:     if (defined($new_value)) {
  336: 	$self->{'outer_border'} = $new_value;
  337:     }
  338:     return $self->{'outer_border'};
  339: }
  340: 
  341: 
  342: =pod
  343: 
  344: =head2 cell_border
  345: 
  346: Set or get the presence of a request for cells to have borders
  347: drawn around them.  If a paramter is passed, it will be treated as
  348: a new value for the cell border configuration.  Regardless,the final
  349: value of that configuration parameter is returned.
  350: 
  351: =head3 Examples:
  352: 
  353:  my $cell_border = $table->cell_border(); # ask if cell borders are requested.
  354:  $table->cell_border(1);	# Request cell borders.
  355: 
  356: =cut
  357: 
  358: sub cell_border {
  359:     my ($self, $new_value) = @_;
  360:     if($tracing) {&Apache::lonnet::logthis("cell_border: $new_value"); }
  361:     if (defined($new_value)) {
  362: 	$self->{'inner_border'} = $new_value;
  363:     }
  364:     return $self->{'inner_border'};
  365: }
  366: 
  367: =pod
  368: 
  369: =head2 caption
  370: 
  371: Gets and/or sets the caption string for the table.  The caption string appears to label
  372: the table.  If a parameter is supplied it will become the new caption string.k
  373: 
  374: =head3 Examples:
  375: 
  376: 
  377:   $my caption = $table->caption();
  378:   $table->caption("This is the new table caption");
  379: 
  380: =cut
  381: 
  382: sub caption {
  383:     my ($self, $new_value) = @_;
  384: 
  385:     if($tracing) {&Apache::lonnet::logthis("caption: $new_value"); }
  386:     if (defined($new_value)) {
  387: 	$self->{'caption'} = $new_value;
  388:     }
  389: 
  390:     return $self->{'caption'};
  391: }
  392: 
  393: =pod
  394: 
  395: =head2 theme
  396: 
  397: Gets and optionally sets the table theme.  The table theme describes how the
  398: table will be typset by the table package.  If a parameter is supplied it
  399: will be the new theme selection.
  400: 
  401: =head3 Examples:
  402: 
  403:   my $theme = $table->theme();
  404:   $table->theme("Dresden");
  405: 
  406: =cut
  407: 
  408: sub theme {
  409:     my ($self, $new_value) = @_;
  410:     if($tracing) {&Apache::lonnet::logthis("theme $new_value"); }
  411:     if (defined($new_value)) {
  412: 	$self->{'theme'} = $new_value;
  413:     }
  414:     return $self->{'theme'};
  415: }
  416: 
  417: =pod
  418: 
  419: =head 2 width
  420: 
  421: Gets and optionally sets the width of the table.
  422: 
  423: =head 3 Examples:
  424: 
  425:  my $newwidth = $table->width("10cm");   # 10cm width returns "10cm".
  426: 
  427: =cut
  428: sub width {
  429:     my ($self, $new_value) = @_;
  430:     if($tracing) {&Apache::lonnet::logthis("width = $new_value"); }
  431: 
  432:     if (defined($new_value)) {
  433: 	$self->{'width'} = $new_value;
  434:     }
  435:     return $self->{'width'}; 	# Could be undef.
  436: }
  437: 
  438: =pod
  439: 
  440: =head2 start_row
  441: 
  442: Begins a new row in the table.  If a row is already open, that row is
  443: closed off prior to starting the new row.  Rows can have the following attributes
  444: which are specified by an optional hash passed in to this function.
  445: 
  446: =over 3
  447: 
  448: =item default_halign
  449: 
  450: The default horizontal alignment of the row. This can be "left", "center", or "right"
  451: 
  452: =item default_valign
  453: 
  454: The default vertical alignment of the row.  This can be "top", "center", or "bottom"
  455: 
  456: =back
  457: 
  458: =head3 Examples:
  459: 
  460:   $table_start_row();                  # no attributes.
  461:   $table_start({default_halign => "center",
  462:                 default_valign => "bottom"}); # Create setting the attrbutes.
  463: 
  464: =cut
  465: 
  466: sub start_row {
  467:     my ($self, $config) = @_;
  468:     if($tracing) {&Apache::lonnet::logthis("start_row"); }
  469:     if ($self->{'row_open'}) { 
  470: 	$self->end_row();
  471:     }
  472:     my $row_hash = {
  473: 	default_halign => "left",
  474: 	default_valign => "top",
  475: 	cell_width     =>  0,
  476: 	cells          => []
  477:     };
  478: 
  479:     # Override the defaults if the config hash is present:
  480: 
  481:     if (defined($config)) {
  482: 	foreach my $key  (keys %$config) {
  483: 	    $row_hash->{$key} = $config->{$key};
  484: 	}
  485:     }
  486: 
  487:     
  488:     my $rows = $self->{'rows'};
  489:     push(@$rows, $row_hash);
  490: 
  491:     $self->{"row_open"} = 1;	# Row is now open and ready for business.
  492: }
  493: 
  494: =pod
  495: 
  496: =head2  end_row 
  497: 
  498: Closes off a row.  Once closed, cells cannot be added to this row again.
  499: 
  500: =head3 Examples:
  501: 
  502:    $table->end_row();
  503: 
  504: 
  505: =cut
  506: 
  507: sub end_row {
  508:     my ($self) = @_;
  509:     if($tracing) {&Apache::lonnet::logthis("end_row"); }
  510:     if ($self->{'row_open'}) {
  511: 	
  512: 	# Mostly we need to determine if this row has the maximum
  513: 	# cell count of any row in existence in the table:
  514: 
  515: 	my $row        = $self->{'rows'}->[-1];
  516: 	my $cells      = $row->{'cells'};
  517: 
  518: 	if ($row->{'cell_width'} > $self->{'column_count'}) {
  519: 	    $self->{'column_count'} = $row->{'cell_width'};
  520: 	}
  521: 
  522: 	$self->{'row_open'} = 0;;
  523:     }
  524: }
  525: 
  526: =pod
  527: 
  528: =head2 configure_row
  529: 
  530: Modify the configuration of a row.   If a row is not open, a new one will be opened.
  531: 
  532: =head3 Parameters:
  533: 
  534: config_hash - A hash that contains new values for the set of row confiuguration 
  535: items to be modified.  There is currently no check/penalty for items that are not in
  536: the set of defined configuration properties which are:
  537: 
  538: =over 2
  539: 
  540: =item default_halign
  541: 
  542: The default horizontal alignment for text in  cells in the row.  This can be any of:
  543: "left", "right" or "center".
  544: 
  545: =item default_valign
  546: 
  547: The default vertical alignment for text in cells in the row.  This can be any of:
  548: 
  549: "top", "bottom" or "center"
  550: 
  551: 
  552: =back 
  553: 
  554: =cut
  555: 
  556: sub configure_row {
  557:     my ($self, $config) = @_;
  558:     if($tracing) {&Apache::lonnet::logthis("configure_row");}
  559:     if (!$self->{'row_open'}) {
  560: 	$self->start_row();
  561:     }
  562:     
  563:     my $row = $self->{'rows'}[-1];
  564:     foreach my $config_item (keys %$config) {
  565: 	$row->{$config_item} = $config->{$config_item};
  566:     }
  567: }
  568: 
  569: 
  570: =pod
  571: 
  572: =head2 add_cell
  573: 
  574: Add a new cell to a row.  If there is a row above us, we need to 
  575: watch out for row spans that may force additional blank cell entries
  576: to fill in the span. 
  577: 
  578: =head3 Parameters:
  579: 
  580: =over 2
  581: 
  582: =item text
  583: 
  584: Text to put in the cell.
  585: 
  586: =item cell_config
  587: 
  588: Hash of configuration options that override the defaults.   The recognized options,
  589: and their defaults are:
  590: 
  591: =over 2
  592: 
  593: =item halign 
  594: 
  595: If nonblank overrides the row's default for the cell's horizontal alignment.
  596: 
  597: =item valign
  598: 
  599: If nonblank, overrides the row's default for the cdell's vertical alignment.
  600: 
  601: =item rowspan
  602: 
  603: Number of rows the cell spans.
  604: 
  605: =item colspan
  606: 
  607: Number of columns the cell spans.
  608: 
  609: =item width
  610: 
  611: LaTeX specification of the width of the cell.
  612: Note that if there is a colspan this width is going to be equally divided
  613: over the widths of the columnsn in the span.
  614: Note as well that if width specification conflict, the last one specified wins...silently.
  615: 
  616: =back
  617: 
  618: =cut
  619: 
  620: sub add_cell {
  621:     my ($self, $text, $config) = @_;
  622: 
  623:     if($tracing) {&Apache::lonnet::logthis("add_cell : $text"); }
  624: 
  625:     # If a row is not open, we must open it:
  626: 
  627:     if (!$self->{'row_open'}) {
  628: 	$self->start_row();
  629:     }
  630:     my $rows          = $self->{'rows'};
  631:     my $current_row   = $rows->[-1];
  632:     my $current_cells = $current_row->{'cells'}; 
  633:     my $last_coord    = $current_row->{'cell_width'};
  634: 
  635:     #  We have to worry about row spans if there is a prior row:
  636: 
  637:     if (scalar(@$rows) > 1) {
  638: 
  639: 	my $last_row = $rows->[-2];
  640: 	if ($last_coord < $last_row->{'cell_width'}) {
  641: 	    my $prior_coord       = 0;
  642: 	    my $prior_cell_index  = 0;
  643: 	    while ($prior_coord <= $last_coord) {
  644: 		
  645: 		# Pull a cell down if it's coord matches our start coord
  646: 		# And there's a row span > 1.
  647: 		# Having done so, we adjust our $last_coord to match the
  648: 		# end point of the pulled down cell.
  649: 
  650: 		my $prior_cell = $last_row->{'cells'}->[$prior_cell_index];
  651: 		if (!defined($prior_cell)) {
  652: 		    last;
  653: 		}
  654: 		if (($prior_cell->{'start_col'} == $last_coord) &&
  655: 		    ($prior_cell->{'rowspan'}  > 1)) {
  656: 		    
  657: 		    #  Need to drop the cell down
  658: 
  659: 		    my %dropped_down_cell = %$prior_cell;
  660: 		    $dropped_down_cell{'rowspan'}--;
  661: 		    $dropped_down_cell{'contents'} = '';
  662: 
  663: 		    push(@$current_cells, \%dropped_down_cell);
  664: 		    $last_coord += $dropped_down_cell{'colspan'};
  665: 		    $current_row->{'cell_width'} = $last_coord;
  666: 		    
  667: 		}
  668: 		$prior_coord += $prior_cell->{'colspan'};
  669: 		$prior_cell_index++;
  670: 	    }
  671: 	}
  672: 
  673:     }
  674: 
  675:     #
  676:     # Now we're ready to build up our cell:
  677: 
  678:     my $cell = {
  679: 	rowspan    => 1,
  680: 	colspan    => 1,
  681: 	start_col  => $last_coord,
  682: 	contents   => $text
  683:     };
  684:     
  685:     if (defined($config)) {
  686: 	foreach my $key (keys(%$config)) {
  687:             if ($key eq 'colspan') {
  688:                 next if ($config->{$key} == 0);
  689:             }
  690: 	    $cell->{$key} = $config->{$key};
  691: 	}
  692:     }
  693: 
  694:     $current_row->{'cell_width'} += $cell->{'colspan'};
  695: 
  696: 
  697:     #
  698:     # Process the width if it exists.  If supplied it must be of the form:
  699:     #   float units
  700:     # Where units can be in, cm or mm.
  701:     # Regardless of the supplied units we will normalize to cm.
  702:     # This allows computation on units at final table generation time.
  703:     #
  704: 
  705:     if (exists($cell->{'width'})) {
  706: 	my $width;
  707: 	my $widthcm;
  708: 	$width   = $config->{'width'};
  709: 	$widthcm = $self->size_to_cm($width);
  710: 	
  711: 	# If there's a column span, the actual width is divided by the span
  712: 	# and applied to each of the columns in the span.
  713: 
  714: 	$widthcm = $widthcm / $cell->{'colspan'};
  715: 	for (my $i = $last_coord; $i < $last_coord + $cell->{'colspan'}; $i++) {
  716: 	    $self->{'col_widths'}->{$i} = $widthcm; 
  717: 	}
  718: 	
  719:     }
  720: 
  721:     push(@$current_cells, $cell);
  722: 
  723:     if ($tracing) { &Apache::lonnet::logthis("add_cell done"); }
  724: }
  725: 
  726: 
  727: =pod
  728: 
  729: =head2  append_cell_text
  730: 
  731: Sometimes it's necessary to create/configure the cell and then later add text to it.
  732: This sub allows text to be appended to the most recently created cell.
  733: 
  734: =head3 Parameters
  735: 
  736: The text to add to the cell.
  737: 
  738: =cut
  739: sub append_cell_text {
  740:     my ($this, $text) = @_;
  741: 
  742:     if($tracing) {&Apache::lonnet::logthis("append_cell_text: $text"); }
  743:     my $rows         = $this->{'rows'};
  744:     my $current_row  = $rows->[-1];
  745:     my $cells        = $current_row->{'cells'};
  746:     my $current_cell = $cells->[-1];
  747:     $current_cell->{'contents'} .= $text;
  748:     
  749: }
  750: 
  751: 
  752: =pod
  753: 
  754: =head2 generate
  755: 
  756: Call this when the structures for the table have been built.
  757: This will generate and return the table object that can be used
  758: to generate the table.  Returning the table object allows for
  759: a certain amount of testing to be done on the generated table.
  760: The caller can then ask the table object to generate LaTeX.
  761: 
  762: =cut
  763: sub generate {
  764:     my ($this) = @_;
  765:     my $useP   = 0;
  766: 
  767:     my $colunits = 'cm';	# All widths get normalized to cm.
  768:     my $tablewidth;
  769: 
  770:     if($tracing) {&Apache::lonnet::logthis("generate"); }
  771:     my $table = Apache::lonlatextable->new();
  772: 
  773: 
  774:     # Add the caption if supplied.
  775: 
  776:     if ($this->{'caption'} ne "") {
  777: 	$table->set_caption($this->caption);
  778:     }
  779:     
  780:     # Set the width if defined:
  781: 
  782:     my $default_width;
  783:     my $colwidths        = $this->{'col_widths'};
  784:     if (defined ($this->{'width'})) {
  785: 	$tablewidth = $this->{'width'};
  786: 	$tablewidth = $this->size_to_cm($tablewidth);
  787: 
  788: 	$useP = 1;
  789: 
  790: 	# Figure out the default width for a column with unspecified
  791: 	# We take the initially specified widths and sum them up.
  792: 	# This is subtracted from total width  above.
  793: 	# If the result is negative we're going to allow a minimum of 2.54cm for
  794: 	# each column and make the table spill appropriately.  
  795: 	# This (like a riot) is an ugly thing but I'm open to suggestions about
  796: 	# how to handle it better (e.g. scaling down requested widths?).
  797: 
  798: 	my $specified_width = 0.0;
  799: 	my $specified_cols   = 0;
  800: 	foreach my $col (keys %$colwidths) {
  801: 	    $specified_width = $specified_width + $colwidths->{$col};
  802: 	    $specified_cols++;
  803: 	}
  804: 	my $unspecified_cols = $this->{'column_count'} - $specified_cols;
  805: 
  806: 	#  If zero unspecified cols, we are pretty much done... just have to
  807: 	#  adjust the total width to be specified  width. Otherwise we
  808: 	#  must figure out the default width and total width:
  809: 	#
  810: 	my $total_width;
  811: 	if($unspecified_cols == 0) {
  812: 	    $total_width = $specified_width;
  813: 	} else {
  814: 	    $default_width = ($tablewidth - $specified_width)/$unspecified_cols; #  Could be negative....
  815: 	    $total_width   = $default_width * $unspecified_cols + $specified_width;
  816: 	}
  817: 	
  818: 	# if the default_width is < 0.0 the user has oversubscribed the width of the table with the individual
  819: 	# column.  In this case, we're going to maintain the desired proportions of the user's columns, but 
  820: 	# ensure that the unspecified columns get a fair share of the width..where a fair share is defined as
  821: 	# the total width of the table / unspecified column count.
  822: 	# We figure out what this means in terms of reducing the specified widths by dividing by a constant proportionality.
  823: 	# Note that this cannot happen if the user hasn't specified anywidths as the computation above would then
  824: 	# just make all columns equal fractions of the total table width.
  825: 
  826: 	if ($default_width < 0) {
  827: 	    $default_width = ($tablewidth/$unspecified_cols);                     # 'fair' default width.
  828: 	    my $width_remaining = $tablewidth - $default_width*$unspecified_cols; # What's left for the specified cols.
  829: 	    my $reduction       = $tablewidth/$width_remaining;                    # Reduction fraction for specified cols
  830: 	    foreach my $col (keys %$colwidths) {
  831: 		$colwidths->{$col} = $colwidths->{$col}/$reduction;
  832: 	    }
  833: 	    
  834:         }
  835:     }
  836: 
  837: 	
  838: 
  839: 
  840:     # Build up the data:
  841: 
  842:     my @data;
  843:     my $rows      = $this->{'rows'};
  844:     my $row_count = scalar(@$rows);
  845:     my $inner_border = $this->{'inner_border'};
  846:     my $outer_border = $this->{'outer_border'};
  847:     my $column_count = $this->{'column_count'};
  848: 
  849:     # Add a top line if the outer or inner border is enabled:
  850: 
  851:     if ($outer_border || $inner_border) {
  852: 	push(@data, ["\\cline{1-$column_count}"]);	     
  853: 
  854:     }
  855: 
  856:     for (my $row = 0; $row < $row_count; $row++) {
  857: 	my @row;
  858: 	my $cells      = $rows->[$row]->{'cells'};
  859: 	my $def_halign = $rows->[$row]->{'default_halign'};
  860: 	my $cell_count = scalar(@$cells);
  861: 	my $startcol   = 1;
  862: 	my @underlines;		# Array of \cline cells if cellborder on.
  863: 
  864: 
  865: 
  866: 	for (my $cell  = 0; $cell < $cell_count; $cell++) {
  867: 	    my $contents = $cells->[$cell]->{'contents'};
  868: 
  869: 	    #
  870: 	    #  Cell alignment is the default alignment unless
  871: 	    #  explicitly specified in the cell.
  872: 	    #  NOTE: at this point I don't know how to do vert alignment.
  873: 	    #
  874: 
  875: 	    my $halign   = $def_halign;
  876: 	    if (defined ($cells->[$cell]->{'halign'})) {
  877: 		$halign = $cells->[$cell]->{'halign'};
  878: 	    }
  879: 
  880: 	    # Create the horizontal alignment character:
  881: 
  882: 	    my $col_align = 'l';
  883: 	    my $embeddedAlignStart = "";
  884: 	    my $embeddedAlignEnd   = "";
  885: 
  886: 	    if ($halign eq 'right') {
  887: 		$col_align = 'r';
  888:                 $embeddedAlignStart = '\raggedleft';
  889: 	    }
  890: 	    if ($halign eq 'center') {
  891: 		$col_align = 'c';
  892: 		$embeddedAlignStart = '\begin{center}';
  893: 		$embeddedAlignEnd   = '\end{center}';
  894: 	    }
  895: 
  896: 	    # If the width has been specified, turn these into
  897: 	    # para mode; and wrap the contents in the start/stop stuff:
  898: 
  899: 	    if ($useP) {
  900: 		my $cw;
  901: 		if (defined($colwidths->{$cell})) {
  902: 		    $cw = $colwidths->{$cell};
  903: 		} else {
  904: 		    $cw = $default_width;
  905: 		}
  906: 		$cw = $cw * $cells->[$cell]->{'colspan'};
  907: 		$col_align = "p{$cw $colunits}";
  908: 		$contents = $embeddedAlignStart . $contents .  $embeddedAlignEnd;
  909: 	    }
  910: 
  911: 	    if ($inner_border || ($outer_border && ($cell == 0))) {
  912: 		$col_align = '|'.$col_align;
  913: 	    }
  914: 	    if ($inner_border || ($outer_border && ($cell == ($cell_count -1)))) {
  915: 		$col_align = $col_align.'|';
  916: 	    }
  917: 
  918: 	    #factor in spans:
  919: 
  920: 	    my $cspan    = $cells->[$cell]->{'colspan'};
  921: 	    my $nextcol  = $startcol + $cspan;
  922: 
  923: 	    # If we can avoid the \multicolumn directive that's best as
  924: 	    # that makes some things like \parpic invalid in LaTeX which
  925:             # screws everything up.
  926: 
  927: 	    if (($cspan > 1) || !($col_align =~ /l/)) {
  928: 
  929: 		$contents = '\multicolumn{'.$cspan.'}{'.$col_align.'}{'.$contents.'}';
  930: 
  931: 		# A nasty edge case.  If there's only one cell, the software will assume
  932: 		# we're in complete control of the row so we need to end the row ourselves.
  933: 		
  934: 		if ($cell_count == 1) {
  935: 		    $contents .= '  \\\\';
  936: 		}
  937: 	    }
  938: 	    if ($inner_border && ($cells->[$cell]->{'rowspan'} == 1)) {
  939: 		my $lastcol = $nextcol -1;
  940: 		push(@underlines, "\\cline{$startcol-$lastcol}");
  941: 	    }
  942: 	    $startcol = $nextcol;
  943: 	    # Rowspans should take care of themselves.
  944: 	    
  945: 	    push(@row, $contents);
  946: 
  947: 	}
  948: 	push(@data, \@row);
  949: 	if ($inner_border) {
  950: 	    for (my $i =0; $i < scalar(@underlines); $i++) {
  951: 		push(@data, [$underlines[$i]]);
  952: 	    }
  953: 	}
  954: 
  955:     }
  956:     #
  957:     # Add bottom border if necessary: if the inner border was on, the loops above
  958:     # will have done a bottom line under the last cell.
  959:     #
  960:     if ($outer_border && !$inner_border) {
  961: 	push(@data, ["\\cline{1-$column_count}"]);	     
  962: 
  963:     }
  964:     $table->set_data(\@data);
  965:     
  966:     my $coldef = "";
  967:     if ($outer_border || $inner_border) {
  968: 	$coldef .= '|';
  969:     }
  970:     for (my $i =0; $i < $column_count; $i++) {
  971: 	if ($useP) {
  972: 	    $coldef .= "p{$default_width $colunits}";
  973: 	} else {
  974: 	    $coldef .= 'l';
  975: 	}
  976: 	if ($inner_border || 
  977: 	    ($outer_border && ($i == $column_count-1))) {
  978: 	    $coldef .= '|';
  979: 	}
  980:     }
  981:     $table->{'coldef'} = $coldef;
  982: 
  983:     # Return the table:
  984: 
  985:     if ($tracing) { &Apache::lonnet::logthis("Leaving generate"); }
  986: 
  987: 
  988:     return $table;
  989: 
  990: }
  991: #---------------------------------------------------------------------------
  992: #
  993: #  Private methods:
  994: #
  995: 
  996: # 
  997: # Convert size with units -> size in cm.
  998: # The resulting size is floating point with no  units so that it can be used in
  999: # computation.  Note that an illegal or missing unit is treated silently as
 1000: #  cm for now.
 1001: #
 1002: sub size_to_cm {
 1003:     my ($this, $size_spec) = @_;
 1004:     my ($size, $units) = split(/ /, $size_spec);
 1005:     if (lc($units) eq 'mm') {
 1006: 	return $size / 10.0;
 1007:     }
 1008:     if (lc($units) eq 'in') {
 1009: 	return $size * 2.54;
 1010:     }
 1011:     
 1012:     return $size;		# Default is cm.
 1013: }
 1014: #----------------------------------------------------------------------------
 1015: # The following methods allow for testability.
 1016: 
 1017: 
 1018: sub get_object_attribute {
 1019:     my ($self, $attribute) = @_;
 1020:     if ($tracing > 1) { &Apache::lonnet::logthis("get_object_attribute: $attribute"); }
 1021:     return $self->{$attribute};
 1022: }
 1023: 
 1024: sub get_row {
 1025:     my ($self, $row) = @_;
 1026:     if ($tracing > 1) { &Apache::lonnet::logthis("get_row"); }
 1027: 
 1028:     my $rows = $self->{'rows'};	  # ref to an array....
 1029:     return $rows->[$row];         # ref to the row hash for the selected row.
 1030: }
 1031: 
 1032: #   Mandatory initialization.
 1033: BEGIN{
 1034: }
 1035: 
 1036: 1;
 1037: __END__
 1038:  

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>