File:  [LON-CAPA] / loncom / xml / lontable.pm
Revision 1.16: download - view: text, annotated - select for diffs
Tue Apr 5 10:02:58 2011 UTC (13 years, 1 month ago) by foxr
Branches: MAIN
CVS tags: HEAD
Bug 6317 - Made rules for all but "groups" work correctly.  Have to think
much harder about the data structures needed for row and column groups
(which are currently not processed in printing) to get that last to
work correctly.

    1: # The LearningOnline Network with CAPA
    2: #  Generating TeX tables.
    3: #
    4: # $Id: lontable.pm,v 1.16 2011/04/05 10:02:58 foxr 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: Valid values for the parameter are:
  351: 
  352: =over 2
  353: 
  354: =item 0 - no borders present.
  355: 
  356: =item 1 - All borders (borders around all four sides of the cell.
  357: 
  358: =item 2 - Border at top and bottom of the cell.
  359: 
  360: =item 3 - Border at the left and right sides of the cell.
  361: 
  362: 
  363: =over -2 
  364: 
  365: =head3 Examples:
  366: 
  367:  my $cell_border = $table->cell_border(); # ask if cell borders are requested.
  368:  $table->cell_border(1);	# Request cell borders.
  369: 
  370: =cut
  371: 
  372: sub cell_border {
  373:     my ($self, $new_value) = @_;
  374:     if($tracing) {&Apache::lonnet::logthis("cell_border: $new_value"); }
  375:     if (defined($new_value)) {
  376: 	$self->{'inner_border'} = $new_value;
  377:     }
  378:     return $self->{'inner_border'};
  379: }
  380: 
  381: =pod
  382: 
  383: =head2 caption
  384: 
  385: Gets and/or sets the caption string for the table.  The caption string appears to label
  386: the table.  If a parameter is supplied it will become the new caption string.k
  387: 
  388: =head3 Examples:
  389: 
  390: 
  391:   $my caption = $table->caption();
  392:   $table->caption("This is the new table caption");
  393: 
  394: =cut
  395: 
  396: sub caption {
  397:     my ($self, $new_value) = @_;
  398: 
  399:     if($tracing) {&Apache::lonnet::logthis("caption: $new_value"); }
  400:     if (defined($new_value)) {
  401: 	$self->{'caption'} = $new_value;
  402:     }
  403: 
  404:     return $self->{'caption'};
  405: }
  406: 
  407: =pod
  408: 
  409: =head2 theme
  410: 
  411: Gets and optionally sets the table theme.  The table theme describes how the
  412: table will be typset by the table package.  If a parameter is supplied it
  413: will be the new theme selection.
  414: 
  415: =head3 Examples:
  416: 
  417:   my $theme = $table->theme();
  418:   $table->theme("Dresden");
  419: 
  420: =cut
  421: 
  422: sub theme {
  423:     my ($self, $new_value) = @_;
  424:     if($tracing) {&Apache::lonnet::logthis("theme $new_value"); }
  425:     if (defined($new_value)) {
  426: 	$self->{'theme'} = $new_value;
  427:     }
  428:     return $self->{'theme'};
  429: }
  430: 
  431: =pod
  432: 
  433: =head 2 width
  434: 
  435: Gets and optionally sets the width of the table.
  436: 
  437: =head 3 Examples:
  438: 
  439:  my $newwidth = $table->width("10cm");   # 10cm width returns "10cm".
  440: 
  441: =cut
  442: sub width {
  443:     my ($self, $new_value) = @_;
  444:     if($tracing) {&Apache::lonnet::logthis("width = $new_value"); }
  445: 
  446:     if (defined($new_value)) {
  447: 	$self->{'width'} = $new_value;
  448:     }
  449:     return $self->{'width'}; 	# Could be undef.
  450: }
  451: 
  452: =pod
  453: 
  454: =head2 start_row
  455: 
  456: Begins a new row in the table.  If a row is already open, that row is
  457: closed off prior to starting the new row.  Rows can have the following attributes
  458: which are specified by an optional hash passed in to this function.
  459: 
  460: =over 3
  461: 
  462: =item default_halign
  463: 
  464: The default horizontal alignment of the row. This can be "left", "center", or "right"
  465: 
  466: =item default_valign
  467: 
  468: The default vertical alignment of the row.  This can be "top", "center", or "bottom"
  469: 
  470: =back
  471: 
  472: =head3 Examples:
  473: 
  474:   $table_start_row();                  # no attributes.
  475:   $table_start({default_halign => "center",
  476:                 default_valign => "bottom"}); # Create setting the attrbutes.
  477: 
  478: =cut
  479: 
  480: sub start_row {
  481:     my ($self, $config) = @_;
  482:     if($tracing) {&Apache::lonnet::logthis("start_row"); }
  483:     if ($self->{'row_open'}) { 
  484: 	$self->end_row();
  485:     }
  486:     my $row_hash = {
  487: 	default_halign => "left",
  488: 	default_valign => "top",
  489: 	cell_width     =>  0,
  490: 	cells          => []
  491:     };
  492: 
  493:     # Override the defaults if the config hash is present:
  494: 
  495:     if (defined($config)) {
  496: 	foreach my $key  (keys %$config) {
  497: 	    $row_hash->{$key} = $config->{$key};
  498: 	}
  499:     }
  500: 
  501:     
  502:     my $rows = $self->{'rows'};
  503:     push(@$rows, $row_hash);
  504: 
  505:     $self->{"row_open"} = 1;	# Row is now open and ready for business.
  506: }
  507: 
  508: =pod
  509: 
  510: =head2  end_row 
  511: 
  512: Closes off a row.  Once closed, cells cannot be added to this row again.
  513: 
  514: =head3 Examples:
  515: 
  516:    $table->end_row();
  517: 
  518: 
  519: =cut
  520: 
  521: sub end_row {
  522:     my ($self) = @_;
  523:     if($tracing) {&Apache::lonnet::logthis("end_row"); }
  524:     if ($self->{'row_open'}) {
  525: 	
  526: 	# Mostly we need to determine if this row has the maximum
  527: 	# cell count of any row in existence in the table:
  528: 
  529: 	my $row        = $self->{'rows'}->[-1];
  530: 	my $cells      = $row->{'cells'};
  531: 
  532: 	if ($row->{'cell_width'} > $self->{'column_count'}) {
  533: 	    $self->{'column_count'} = $row->{'cell_width'};
  534: 	}
  535: 
  536: 	$self->{'row_open'} = 0;;
  537:     }
  538: }
  539: 
  540: =pod
  541: 
  542: =head2 configure_row
  543: 
  544: Modify the configuration of a row.   If a row is not open, a new one will be opened.
  545: 
  546: =head3 Parameters:
  547: 
  548: config_hash - A hash that contains new values for the set of row confiuguration 
  549: items to be modified.  There is currently no check/penalty for items that are not in
  550: the set of defined configuration properties which are:
  551: 
  552: =over 2
  553: 
  554: =item default_halign
  555: 
  556: The default horizontal alignment for text in  cells in the row.  This can be any of:
  557: "left", "right" or "center".
  558: 
  559: =item default_valign
  560: 
  561: The default vertical alignment for text in cells in the row.  This can be any of:
  562: 
  563: "top", "bottom" or "center"
  564: 
  565: 
  566: =back 
  567: 
  568: =cut
  569: 
  570: sub configure_row {
  571:     my ($self, $config) = @_;
  572:     if($tracing) {&Apache::lonnet::logthis("configure_row");}
  573:     if (!$self->{'row_open'}) {
  574: 	$self->start_row();
  575:     }
  576:     
  577:     my $row = $self->{'rows'}[-1];
  578:     foreach my $config_item (keys %$config) {
  579: 	$row->{$config_item} = $config->{$config_item};
  580:     }
  581: }
  582: 
  583: 
  584: =pod
  585: 
  586: =head2 add_cell
  587: 
  588: Add a new cell to a row.  If there is a row above us, we need to 
  589: watch out for row spans that may force additional blank cell entries
  590: to fill in the span. 
  591: 
  592: =head3 Parameters:
  593: 
  594: =over 2
  595: 
  596: =item text
  597: 
  598: Text to put in the cell.
  599: 
  600: =item cell_config
  601: 
  602: Hash of configuration options that override the defaults.   The recognized options,
  603: and their defaults are:
  604: 
  605: =over 2
  606: 
  607: =item halign 
  608: 
  609: If nonblank overrides the row's default for the cell's horizontal alignment.
  610: 
  611: =item valign
  612: 
  613: If nonblank, overrides the row's default for the cdell's vertical alignment.
  614: 
  615: =item rowspan
  616: 
  617: Number of rows the cell spans.
  618: 
  619: =item colspan
  620: 
  621: Number of columns the cell spans.
  622: 
  623: =item width
  624: 
  625: LaTeX specification of the width of the cell.
  626: Note that if there is a colspan this width is going to be equally divided
  627: over the widths of the columnsn in the span.
  628: Note as well that if width specification conflict, the last one specified wins...silently.
  629: 
  630: =back
  631: 
  632: =cut
  633: 
  634: sub add_cell {
  635:     my ($self, $text, $config) = @_;
  636: 
  637:     if($tracing) {&Apache::lonnet::logthis("add_cell : $text"); }
  638: 
  639:     # If a row is not open, we must open it:
  640: 
  641:     if (!$self->{'row_open'}) {
  642: 	$self->start_row();
  643:     }
  644:     my $rows          = $self->{'rows'};
  645:     my $current_row   = $rows->[-1];
  646:     my $current_cells = $current_row->{'cells'}; 
  647:     my $last_coord    = $current_row->{'cell_width'};
  648: 
  649:     #  We have to worry about row spans if there is a prior row:
  650: 
  651:     if (scalar(@$rows) > 1) {
  652: 
  653: 	my $last_row = $rows->[-2];
  654: 	if ($last_coord < $last_row->{'cell_width'}) {
  655: 	    my $prior_coord       = 0;
  656: 	    my $prior_cell_index  = 0;
  657: 	    while ($prior_coord <= $last_coord) {
  658: 		
  659: 		# Pull a cell down if it's coord matches our start coord
  660: 		# And there's a row span > 1.
  661: 		# Having done so, we adjust our $last_coord to match the
  662: 		# end point of the pulled down cell.
  663: 
  664: 		my $prior_cell = $last_row->{'cells'}->[$prior_cell_index];
  665: 		if (!defined($prior_cell)) {
  666: 		    last;
  667: 		}
  668: 		if (($prior_cell->{'start_col'} == $last_coord) &&
  669: 		    ($prior_cell->{'rowspan'}  > 1)) {
  670: 		    
  671: 		    #  Need to drop the cell down
  672: 
  673: 		    my %dropped_down_cell = %$prior_cell;
  674: 		    $dropped_down_cell{'rowspan'}--;
  675: 		    $dropped_down_cell{'contents'} = '';
  676: 
  677: 		    push(@$current_cells, \%dropped_down_cell);
  678: 		    $last_coord += $dropped_down_cell{'colspan'};
  679: 		    $current_row->{'cell_width'} = $last_coord;
  680: 		    
  681: 		}
  682: 		$prior_coord += $prior_cell->{'colspan'};
  683: 		$prior_cell_index++;
  684: 	    }
  685: 	}
  686: 
  687:     }
  688: 
  689:     #
  690:     # Now we're ready to build up our cell:
  691: 
  692:     my $cell = {
  693: 	rowspan    => 1,
  694: 	colspan    => 1,
  695: 	start_col  => $last_coord,
  696: 	contents   => $text
  697:     };
  698:     
  699:     if (defined($config)) {
  700: 	foreach my $key (keys(%$config)) {
  701:             if ($key eq 'colspan') {
  702:                 next if ($config->{$key} == 0);
  703:             }
  704: 	    $cell->{$key} = $config->{$key};
  705: 	}
  706:     }
  707: 
  708:     $current_row->{'cell_width'} += $cell->{'colspan'};
  709: 
  710: 
  711:     #
  712:     # Process the width if it exists.  If supplied it must be of the form:
  713:     #   float units
  714:     # Where units can be in, cm or mm.
  715:     # Regardless of the supplied units we will normalize to cm.
  716:     # This allows computation on units at final table generation time.
  717:     #
  718: 
  719:     if (exists($cell->{'width'})) {
  720: 	my $width;
  721: 	my $widthcm;
  722: 	$width   = $config->{'width'};
  723: 	$widthcm = $self->size_to_cm($width);
  724: 	
  725: 	# If there's a column span, the actual width is divided by the span
  726: 	# and applied to each of the columns in the span.
  727: 
  728: 	$widthcm = $widthcm / $cell->{'colspan'};
  729: 	for (my $i = $last_coord; $i < $last_coord + $cell->{'colspan'}; $i++) {
  730: 	    $self->{'col_widths'}->{$i} = $widthcm; 
  731: 	}
  732: 	
  733:     }
  734: 
  735:     push(@$current_cells, $cell);
  736: 
  737:     if ($tracing) { &Apache::lonnet::logthis("add_cell done"); }
  738: }
  739: 
  740: 
  741: =pod
  742: 
  743: =head2  append_cell_text
  744: 
  745: Sometimes it's necessary to create/configure the cell and then later add text to it.
  746: This sub allows text to be appended to the most recently created cell.
  747: 
  748: =head3 Parameters
  749: 
  750: The text to add to the cell.
  751: 
  752: =cut
  753: sub append_cell_text {
  754:     my ($this, $text) = @_;
  755: 
  756:     if($tracing) {&Apache::lonnet::logthis("append_cell_text: $text"); }
  757:     my $rows         = $this->{'rows'};
  758:     my $current_row  = $rows->[-1];
  759:     my $cells        = $current_row->{'cells'};
  760:     my $current_cell = $cells->[-1];
  761:     $current_cell->{'contents'} .= $text;
  762:     
  763: }
  764: 
  765: 
  766: =pod
  767: 
  768: =head2 generate
  769: 
  770: Call this when the structures for the table have been built.
  771: This will generate and return the table object that can be used
  772: to generate the table.  Returning the table object allows for
  773: a certain amount of testing to be done on the generated table.
  774: The caller can then ask the table object to generate LaTeX.
  775: 
  776: =cut
  777: sub generate {
  778:     my ($this) = @_;
  779:     my $useP   = 0;
  780: 
  781:     my $colunits = 'cm';	# All widths get normalized to cm.
  782:     my $tablewidth;
  783: 
  784:     if($tracing) {&Apache::lonnet::logthis("generate"); }
  785:     my $table = Apache::lonlatextable->new();
  786: 
  787: 
  788:     # Add the caption if supplied.
  789: 
  790:     if ($this->{'caption'} ne "") {
  791: 	$table->set_caption($this->caption);
  792:     }
  793:     
  794:     # Set the width if defined:
  795: 
  796:     my $default_width;
  797:     my $colwidths        = $this->{'col_widths'};
  798:     if (defined ($this->{'width'})) {
  799: 	$tablewidth = $this->{'width'};
  800: 	$tablewidth = $this->size_to_cm($tablewidth);
  801: 
  802: 	$useP = 1;
  803: 
  804: 	# Figure out the default width for a column with unspecified
  805: 	# We take the initially specified widths and sum them up.
  806: 	# This is subtracted from total width  above.
  807: 	# If the result is negative we're going to allow a minimum of 2.54cm for
  808: 	# each column and make the table spill appropriately.  
  809: 	# This (like a riot) is an ugly thing but I'm open to suggestions about
  810: 	# how to handle it better (e.g. scaling down requested widths?).
  811: 
  812: 	my $specified_width = 0.0;
  813: 	my $specified_cols   = 0;
  814: 	foreach my $col (keys %$colwidths) {
  815: 	    $specified_width = $specified_width + $colwidths->{$col};
  816: 	    $specified_cols++;
  817: 	}
  818: 	my $unspecified_cols = $this->{'column_count'} - $specified_cols;
  819: 
  820: 	#  If zero unspecified cols, we are pretty much done... just have to
  821: 	#  adjust the total width to be specified  width. Otherwise we
  822: 	#  must figure out the default width and total width:
  823: 	#
  824: 	my $total_width;
  825: 	if($unspecified_cols == 0) {
  826: 	    $total_width = $specified_width;
  827: 	} else {
  828: 	    $default_width = ($tablewidth - $specified_width)/$unspecified_cols; #  Could be negative....
  829: 	    $total_width   = $default_width * $unspecified_cols + $specified_width;
  830: 	}
  831: 	
  832: 	# if the default_width is < 0.0 the user has oversubscribed the width of the table with the individual
  833: 	# column.  In this case, we're going to maintain the desired proportions of the user's columns, but 
  834: 	# ensure that the unspecified columns get a fair share of the width..where a fair share is defined as
  835: 	# the total width of the table / unspecified column count.
  836: 	# We figure out what this means in terms of reducing the specified widths by dividing by a constant proportionality.
  837: 	# Note that this cannot happen if the user hasn't specified anywidths as the computation above would then
  838: 	# just make all columns equal fractions of the total table width.
  839: 
  840: 	if ($default_width < 0) {
  841: 	    $default_width = ($tablewidth/$unspecified_cols);                     # 'fair' default width.
  842: 	    my $width_remaining = $tablewidth - $default_width*$unspecified_cols; # What's left for the specified cols.
  843: 	    my $reduction       = $tablewidth/$width_remaining;                    # Reduction fraction for specified cols
  844: 	    foreach my $col (keys %$colwidths) {
  845: 		$colwidths->{$col} = $colwidths->{$col}/$reduction;
  846: 	    }
  847: 	    
  848:         }
  849:     }
  850: 
  851: 	
  852: 
  853: 
  854:     # Build up the data:
  855: 
  856:     my @data;
  857:     my $rows      = $this->{'rows'};
  858:     my $row_count = scalar(@$rows);
  859:     my $inner_border = $this->{'inner_border'};
  860:     my $outer_border = $this->{'outer_border'};
  861:     my $column_count = $this->{'column_count'};
  862: 
  863:     my $cell_ul_border = (($inner_border == 1) || ($inner_border == 2)) ? 1 : 0;
  864:     my $cell_lr_border = (($inner_border == 1) || ($inner_border == 3)) ? 1 : 0;
  865:  
  866:     # Add a top line if the outer or inner border is enabled:
  867: 
  868:     if ($outer_border || $cell_ul_border) {
  869: 	push(@data, ["\\cline{1-$column_count}"]);	     
  870: 
  871:     }
  872: 
  873:     for (my $row = 0; $row < $row_count; $row++) {
  874: 	my @row;
  875: 	my $cells      = $rows->[$row]->{'cells'};
  876: 	my $def_halign = $rows->[$row]->{'default_halign'};
  877: 	my $cell_count = scalar(@$cells);
  878: 	my $startcol   = 1;
  879: 	my @underlines;		# Array of \cline cells if cellborder on.
  880: 
  881: 
  882: 
  883: 	for (my $cell  = 0; $cell < $cell_count; $cell++) {
  884: 	    my $contents = $cells->[$cell]->{'contents'};
  885: 
  886: 	    #
  887: 	    #  Cell alignment is the default alignment unless
  888: 	    #  explicitly specified in the cell.
  889: 	    #  NOTE: at this point I don't know how to do vert alignment.
  890: 	    #
  891: 
  892: 	    my $halign   = $def_halign;
  893: 	    if (defined ($cells->[$cell]->{'halign'})) {
  894: 		$halign = $cells->[$cell]->{'halign'};
  895: 	    }
  896: 
  897: 	    # Create the horizontal alignment character:
  898: 
  899: 	    my $col_align = 'l';
  900: 	    my $embeddedAlignStart = "";
  901: 	    my $embeddedAlignEnd   = "";
  902: 
  903: 	    if ($halign eq 'right') {
  904: 		$col_align = 'r';
  905:                 $embeddedAlignStart = '\raggedleft';
  906: 	    }
  907: 	    if ($halign eq 'center') {
  908: 		$col_align = 'c';
  909: 		$embeddedAlignStart = '\begin{center}';
  910: 		$embeddedAlignEnd   = '\end{center}';
  911: 	    }
  912: 
  913: 	    # If the width has been specified, turn these into
  914: 	    # para mode; and wrap the contents in the start/stop stuff:
  915: 
  916: 	    if ($useP) {
  917: 		my $cw;
  918: 		if (defined($colwidths->{$cell})) {
  919: 		    $cw = $colwidths->{$cell};
  920: 		} else {
  921: 		    $cw = $default_width;
  922: 		}
  923: 		$cw = $cw * $cells->[$cell]->{'colspan'};
  924: 		$col_align = "p{$cw $colunits}";
  925: 		$contents = $embeddedAlignStart . $contents .  $embeddedAlignEnd;
  926: 	    }
  927: 
  928: 	    if ($cell_lr_border || ($outer_border && ($cell == 0))) {
  929: 		$col_align = '|'.$col_align;
  930: 	    }
  931: 	    if ($cell_lr_border || ($outer_border && ($cell == ($cell_count -1)))) {
  932: 		$col_align = $col_align.'|';
  933: 	    }
  934: 
  935: 	    #factor in spans:
  936: 
  937: 	    my $cspan    = $cells->[$cell]->{'colspan'};
  938: 	    my $nextcol  = $startcol + $cspan;
  939: 
  940: 	    # If we can avoid the \multicolumn directive that's best as
  941: 	    # that makes some things like \parpic invalid in LaTeX which
  942:             # screws everything up.
  943: 
  944: 	    if (($cspan > 1) || !($col_align =~ /l/)) {
  945: 
  946: 		$contents = '\multicolumn{'.$cspan.'}{'.$col_align.'}{'.$contents.'}';
  947: 
  948: 		# A nasty edge case.  If there's only one cell, the software will assume
  949: 		# we're in complete control of the row so we need to end the row ourselves.
  950: 		
  951: 		if ($cell_count == 1) {
  952: 		    $contents .= '  \\\\';
  953: 		}
  954: 	    }
  955: 	    if ($cell_ul_border && ($cells->[$cell]->{'rowspan'} == 1)) {
  956: 		my $lastcol = $nextcol -1;
  957: 		push(@underlines, "\\cline{$startcol-$lastcol}");
  958: 	    }
  959: 	    $startcol = $nextcol;
  960: 	    # Rowspans should take care of themselves.
  961: 	    
  962: 	    push(@row, $contents);
  963: 
  964: 	}
  965: 	push(@data, \@row);
  966: 	if ($cell_ul_border) {
  967: 	    for (my $i =0; $i < scalar(@underlines); $i++) {
  968: 		push(@data, [$underlines[$i]]);
  969: 	    }
  970: 	}
  971: 
  972:     }
  973:     #
  974:     # Add bottom border if necessary: if the inner border was on, the loops above
  975:     # will have done a bottom line under the last cell.
  976:     #
  977:     if ($outer_border && !$cell_ul_border) {
  978: 	push(@data, ["\\cline{1-$column_count}"]);	     
  979: 
  980:     }
  981:     $table->set_data(\@data);
  982:     
  983:     my $coldef = "";
  984:     if ($outer_border || $cell_lr_border) {
  985: 	$coldef .= '|';
  986:     }
  987:     for (my $i =0; $i < $column_count; $i++) {
  988: 	if ($useP) {
  989: 	    $coldef .= "p{$default_width $colunits}";
  990: 	} else {
  991: 	    $coldef .= 'l';
  992: 	}
  993: 	if ($cell_lr_border || 
  994: 	    ($outer_border && ($i == $column_count-1))) {
  995: 	    $coldef .= '|';
  996: 	}
  997:     }
  998:     $table->{'coldef'} = $coldef;
  999: 
 1000:     # Return the table:
 1001: 
 1002:     if ($tracing) { &Apache::lonnet::logthis("Leaving generate"); }
 1003: 
 1004: 
 1005:     return $table;
 1006: 
 1007: }
 1008: #---------------------------------------------------------------------------
 1009: #
 1010: #  Private methods:
 1011: #
 1012: 
 1013: # 
 1014: # Convert size with units -> size in cm.
 1015: # The resulting size is floating point with no  units so that it can be used in
 1016: # computation.  Note that an illegal or missing unit is treated silently as
 1017: #  cm for now.
 1018: #
 1019: sub size_to_cm {
 1020:     my ($this, $size_spec) = @_;
 1021:     my ($size, $units) = split(/ /, $size_spec);
 1022:     if (lc($units) eq 'mm') {
 1023: 	return $size / 10.0;
 1024:     }
 1025:     if (lc($units) eq 'in') {
 1026: 	return $size * 2.54;
 1027:     }
 1028:     
 1029:     return $size;		# Default is cm.
 1030: }
 1031: #----------------------------------------------------------------------------
 1032: # The following methods allow for testability.
 1033: 
 1034: 
 1035: sub get_object_attribute {
 1036:     my ($self, $attribute) = @_;
 1037:     if ($tracing > 1) { &Apache::lonnet::logthis("get_object_attribute: $attribute"); }
 1038:     return $self->{$attribute};
 1039: }
 1040: 
 1041: sub get_row {
 1042:     my ($self, $row) = @_;
 1043:     if ($tracing > 1) { &Apache::lonnet::logthis("get_row"); }
 1044: 
 1045:     my $rows = $self->{'rows'};	  # ref to an array....
 1046:     return $rows->[$row];         # ref to the row hash for the selected row.
 1047: }
 1048: 
 1049: #   Mandatory initialization.
 1050: BEGIN{
 1051: }
 1052: 
 1053: 1;
 1054: __END__
 1055:  

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