Annotation of loncom/xml/lontable.pm, revision 1.14
1.1 foxr 1: # The LearningOnline Network with CAPA
2: # Generating TeX tables.
3: #
1.14 ! foxr 4: # $Id: lontable.pm,v 1.13 2010/09/28 22:55:41 raeburn Exp $
1.1 foxr 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
1.12 foxr 41: # LaTeX tables using the Apache::lonlatextable package. A prerequisite is that
1.5 foxr 42: # the print generator must have added the following to the LaTeX
1.1 foxr 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;
1.12 foxr 58: use Apache::lonlatextable;
1.8 foxr 59: use Apache::lonnet; # for trace logging.
1.1 foxr 60:
1.14 ! foxr 61: my $tracing = 0; # Set to 1 to enable log tracing. 2 for local sub tracing.
1.1 foxr 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
1.12 foxr 68: In this implementation, we use the Apache::lonlatextable package to do the actual final formatting.
1.1 foxr 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:
1.3 foxr 94: =item add_cell
95:
96: Add and configure a cell to the current row.6
97:
1.1 foxr 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:
1.7 foxr 113:
1.1 foxr 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
1.12 foxr 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
1.1 foxr 136: when the table text is generated.
137:
1.7 foxr 138: =item width
139:
1.8 foxr 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.
1.7 foxr 144:
1.1 foxr 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:
1.6 foxr 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:
1.1 foxr 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
1.3 foxr 200: 0
1.1 foxr 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:
1.6 foxr 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:
1.1 foxr 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:
1.3 foxr 219: =item header
220:
221: If present, the row is a 'header' that is it was made via the
222: <th> tag.
223:
1.1 foxr 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:
1.6 foxr 241: =item start_col
242:
243: The starting column of the cell in the table grid.
244:
1.1 foxr 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:
1.8 foxr 258:
1.1 foxr 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,
1.2 foxr 265: inner_border => 0,
1.1 foxr 266: caption => "",
1.11 foxr 267: theme => "plain",
1.1 foxr 268: column_count => 0,
269: row_open => 0,
270: rows => [],
1.14 ! foxr 271: col_widths => {}
1.1 foxr 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:
1.3 foxr 283:
1.1 foxr 284: #-------------------------------------------------------------------------
285: #
286: # Methods that get/set table global configuration.
1.2 foxr 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:
1.8 foxr 306: if ($tracing) {&Apache::lonnet::logthis("alignment = $new_value");}
307:
1.2 foxr 308: if (defined($new_value)) {
1.5 foxr 309: $self->{'alignment'} = $new_value;
1.2 foxr 310: }
1.5 foxr 311: return $self->{'alignment'};
1.2 foxr 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:
1.8 foxr 333: if ($tracing) {&Apache::lonnet::logthis("table_border $new_value");}
334:
1.2 foxr 335: if (defined($new_value)) {
1.5 foxr 336: $self->{'outer_border'} = $new_value;
1.2 foxr 337: }
1.5 foxr 338: return $self->{'outer_border'};
1.2 foxr 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:
1.4 foxr 353: my $cell_border = $table->cell_border(); # ask if cell borders are requested.
1.2 foxr 354: $table->cell_border(1); # Request cell borders.
355:
356: =cut
357:
1.4 foxr 358: sub cell_border {
1.2 foxr 359: my ($self, $new_value) = @_;
1.8 foxr 360: if($tracing) {&Apache::lonnet::logthis("cell_border: $new_value"); }
1.2 foxr 361: if (defined($new_value)) {
1.5 foxr 362: $self->{'inner_border'} = $new_value;
1.2 foxr 363: }
1.5 foxr 364: return $self->{'inner_border'};
1.2 foxr 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:
1.8 foxr 385: if($tracing) {&Apache::lonnet::logthis("caption: $new_value"); }
1.2 foxr 386: if (defined($new_value)) {
1.5 foxr 387: $self->{'caption'} = $new_value;
1.2 foxr 388: }
389:
1.5 foxr 390: return $self->{'caption'};
1.2 foxr 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:
1.1 foxr 402:
1.2 foxr 403: my $theme = $table->theme();
404: $table->theme("Dresden");
405:
406: =cut
407:
408: sub theme {
409: my ($self, $new_value) = @_;
1.8 foxr 410: if($tracing) {&Apache::lonnet::logthis("theme $new_value"); }
1.2 foxr 411: if (defined($new_value)) {
1.5 foxr 412: $self->{'theme'} = $new_value;
1.2 foxr 413: }
1.5 foxr 414: return $self->{'theme'};
1.2 foxr 415: }
416:
417: =pod
418:
1.7 foxr 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) = @_;
1.8 foxr 430: if($tracing) {&Apache::lonnet::logthis("width = $new_value"); }
431:
1.7 foxr 432: if (defined($new_value)) {
433: $self->{'width'} = $new_value;
434: }
435: return $self->{'width'}; # Could be undef.
436: }
437:
438: =pod
439:
1.2 foxr 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 {
1.5 foxr 467: my ($self, $config) = @_;
1.8 foxr 468: if($tracing) {&Apache::lonnet::logthis("start_row"); }
1.5 foxr 469: if ($self->{'row_open'}) {
1.4 foxr 470: $self->end_row();
1.2 foxr 471: }
472: my $row_hash = {
473: default_halign => "left",
474: default_valign => "top",
1.6 foxr 475: cell_width => 0,
1.2 foxr 476: cells => []
477: };
478:
479: # Override the defaults if the config hash is present:
480:
1.5 foxr 481: if (defined($config)) {
482: foreach my $key (keys %$config) {
483: $row_hash->{$key} = $config->{$key};
1.2 foxr 484: }
485: }
1.5 foxr 486:
1.2 foxr 487:
1.5 foxr 488: my $rows = $self->{'rows'};
1.2 foxr 489: push(@$rows, $row_hash);
490:
1.5 foxr 491: $self->{"row_open"} = 1; # Row is now open and ready for business.
1.2 foxr 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:
1.4 foxr 502: $table->end_row();
1.2 foxr 503:
504:
505: =cut
506:
1.4 foxr 507: sub end_row {
1.2 foxr 508: my ($self) = @_;
1.8 foxr 509: if($tracing) {&Apache::lonnet::logthis("end_row"); }
1.5 foxr 510: if ($self->{'row_open'}) {
1.2 foxr 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:
1.6 foxr 515: my $row = $self->{'rows'}->[-1];
1.5 foxr 516: my $cells = $row->{'cells'};
1.3 foxr 517:
1.6 foxr 518: if ($row->{'cell_width'} > $self->{'column_count'}) {
519: $self->{'column_count'} = $row->{'cell_width'};
1.2 foxr 520: }
521:
1.5 foxr 522: $self->{'row_open'} = 0;;
1.2 foxr 523: }
524: }
525:
526: =pod
527:
1.3 foxr 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:
1.2 foxr 533:
1.3 foxr 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:
1.2 foxr 537:
1.3 foxr 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:
1.14 ! foxr 551:
1.3 foxr 552: =back
1.2 foxr 553:
554: =cut
555:
1.3 foxr 556: sub configure_row {
557: my ($self, $config) = @_;
1.8 foxr 558: if($tracing) {&Apache::lonnet::logthis("configure_row");}
1.5 foxr 559: if (!$self->{'row_open'}) {
1.3 foxr 560: $self->start_row();
561: }
562:
1.5 foxr 563: my $row = $self->{'rows'}[-1];
1.3 foxr 564: foreach my $config_item (keys %$config) {
565: $row->{$config_item} = $config->{$config_item};
566: }
1.2 foxr 567: }
1.1 foxr 568:
569:
1.3 foxr 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:
1.14 ! foxr 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:
1.3 foxr 616: =back
617:
618: =cut
619:
620: sub add_cell {
621: my ($self, $text, $config) = @_;
622:
1.8 foxr 623: if($tracing) {&Apache::lonnet::logthis("add_cell : $text"); }
624:
1.3 foxr 625: # If a row is not open, we must open it:
626:
1.5 foxr 627: if (!$self->{'row_open'}) {
1.3 foxr 628: $self->start_row();
629: }
1.6 foxr 630: my $rows = $self->{'rows'};
631: my $current_row = $rows->[-1];
1.5 foxr 632: my $current_cells = $current_row->{'cells'};
1.6 foxr 633: my $last_coord = $current_row->{'cell_width'};
1.3 foxr 634:
1.6 foxr 635: # We have to worry about row spans if there is a prior row:
1.3 foxr 636:
1.6 foxr 637: if (scalar(@$rows) > 1) {
1.3 foxr 638:
1.6 foxr 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];
1.8 foxr 651: if (!defined($prior_cell)) {
652: last;
653: }
1.6 foxr 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: }
1.3 foxr 671: }
1.6 foxr 672:
1.3 foxr 673: }
1.6 foxr 674:
1.3 foxr 675: #
676: # Now we're ready to build up our cell:
677:
678: my $cell = {
679: rowspan => 1,
680: colspan => 1,
1.6 foxr 681: start_col => $last_coord,
1.3 foxr 682: contents => $text
683: };
684:
685: if (defined($config)) {
686: foreach my $key (keys(%$config)) {
687: $cell->{$key} = $config->{$key};
688: }
689: }
1.14 ! foxr 690:
1.6 foxr 691: $current_row->{'cell_width'} += $cell->{'colspan'};
692:
1.14 ! foxr 693:
! 694: #
! 695: # Process the width if it exists. If supplied it must be of the form:
! 696: # float units
! 697: # Where units can be in, cm or mm.
! 698: # Regardless of the supplied units we will normalize to cm.
! 699: # This allows computation on units at final table generation time.
! 700: #
! 701:
! 702: if (exists($cell->{'width'})) {
! 703: my $width;
! 704: my $widthcm;
! 705: $width = $config->{'width'};
! 706: $widthcm = $self->size_to_cm($width);
! 707:
! 708: # If there's a column span, the actual width is divided by the span
! 709: # and applied to each of the columns in the span.
! 710:
! 711: $widthcm = $widthcm / $cell->{'colspan'};
! 712: for (my $i = $last_coord; $i < $last_coord + $cell->{'colspan'}; $i++) {
! 713: $self->{'col_widths'}->{$i} = $widthcm;
! 714: }
! 715:
! 716: }
! 717:
1.3 foxr 718: push(@$current_cells, $cell);
1.8 foxr 719:
720: if ($tracing) { &Apache::lonnet::logthis("add_cell done"); }
1.3 foxr 721: }
722:
1.8 foxr 723:
724: =pod
725:
726: =head2 append_cell_text
727:
728: Sometimes it's necessary to create/configure the cell and then later add text to it.
729: This sub allows text to be appended to the most recently created cell.
730:
731: =head3 Parameters
732:
733: The text to add to the cell.
734:
735: =cut
736: sub append_cell_text {
737: my ($this, $text) = @_;
738:
739: if($tracing) {&Apache::lonnet::logthis("append_cell_text: $text"); }
740: my $rows = $this->{'rows'};
741: my $current_row = $rows->[-1];
742: my $cells = $current_row->{'cells'};
743: my $current_cell = $cells->[-1];
744: $current_cell->{'contents'} .= $text;
745:
746: }
747:
748:
1.6 foxr 749: =pod
750:
751: =head2 generate
752:
753: Call this when the structures for the table have been built.
754: This will generate and return the table object that can be used
755: to generate the table. Returning the table object allows for
756: a certain amount of testing to be done on the generated table.
757: The caller can then ask the table object to generate LaTeX.
758:
759: =cut
760: sub generate {
761: my ($this) = @_;
1.8 foxr 762: my $useP = 0;
1.14 ! foxr 763:
! 764: my $colunits = 'cm'; # All widths get normalized to cm.
! 765: my $tablewidth;
1.6 foxr 766:
1.8 foxr 767: if($tracing) {&Apache::lonnet::logthis("generate"); }
1.12 foxr 768: my $table = Apache::lonlatextable->new();
769:
1.8 foxr 770:
1.7 foxr 771: # Add the caption if supplied.
772:
773: if ($this->{'caption'} ne "") {
774: $table->set_caption($this->caption);
775: }
776:
777: # Set the width if defined:
778:
1.14 ! foxr 779: my $default_width;
! 780: my $colwidths = $this->{'col_widths'};
1.7 foxr 781: if (defined ($this->{'width'})) {
1.14 ! foxr 782: $tablewidth = $this->{'width'};
! 783: $tablewidth = $this->size_to_cm($tablewidth);
! 784:
1.8 foxr 785: $useP = 1;
786:
1.14 ! foxr 787: # Figure out the default width for a column with unspecified
! 788: # We take the initially specified widths and sum them up.
! 789: # This is subtracted from total width above.
! 790: # If the result is negative we're going to allow a minimum of 2.54cm for
! 791: # each column and make the table spill appropriately.
! 792: # This (like a riot) is an ugly thing but I'm open to suggestions about
! 793: # how to handle it better (e.g. scaling down requested widths?).
! 794:
! 795: my $specified_width = 0.0;
! 796: my $specified_cols = 0;
! 797: foreach my $col (keys %$colwidths) {
! 798: $specified_width = $specified_width + $colwidths->{$col};
! 799: $specified_cols++;
! 800: }
! 801: my $unspecified_cols = $this->{'column_count'} - $specified_cols;
! 802:
! 803: # If zero unspecified cols, we are pretty much done... just have to
! 804: # adjust the total width to be specified width. Otherwise we
! 805: # must figure out the default width and total width:
! 806: #
! 807: my $total_width;
! 808: if($unspecified_cols == 0) {
! 809: $total_width = $specified_width;
! 810: } else {
! 811: $default_width = ($tablewidth - $specified_width)/$unspecified_cols; # Could be negative....
! 812: $total_width = $default_width * $unspecified_cols + $specified_width;
! 813: }
! 814:
! 815: # if the default_width is < 0.0 the user has oversubscribed the width of the table with the individual
! 816: # column. In this case, we're going to maintain the desired proportions of the user's columns, but
! 817: # ensure that the unspecified columns get a fair share of the width..where a fair share is defined as
! 818: # the total width of the table / unspecified column count.
! 819: # We figure out what this means in terms of reducing the specified widths by dividing by a constant proportionality.
! 820: # Note that this cannot happen if the user hasn't specified anywidths as the computation above would then
! 821: # just make all columns equal fractions of the total table width.
! 822:
! 823: if ($default_width < 0) {
! 824: $default_width = ($tablewidth/$unspecified_cols); # 'fair' default width.
! 825: my $width_remaining = $tablewidth - $default_width*$unspecified_cols; # What's left for the specified cols.
! 826: my $reduction = $tablewidth/$width_remaining; # Reduction fraction for specified cols
! 827: foreach my $col (keys %$colwidths) {
! 828: $colwidths->{$col} = $colwidths->{$col}/$reduction;
! 829: }
! 830:
! 831: }
1.7 foxr 832: }
833:
1.14 ! foxr 834:
! 835:
! 836:
1.6 foxr 837: # Build up the data:
838:
839: my @data;
840: my $rows = $this->{'rows'};
841: my $row_count = scalar(@$rows);
842: my $inner_border = $this->{'inner_border'};
843: my $outer_border = $this->{'outer_border'};
844: my $column_count = $this->{'column_count'};
845:
1.11 foxr 846: # Add a top line if the outer or inner border is enabled:
847:
848: if ($outer_border || $inner_border) {
849: push(@data, ["\\cline{1-$column_count}"]);
850:
851: }
852:
1.6 foxr 853: for (my $row = 0; $row < $row_count; $row++) {
854: my @row;
855: my $cells = $rows->[$row]->{'cells'};
1.7 foxr 856: my $def_halign = $rows->[$row]->{'default_halign'};
1.6 foxr 857: my $cell_count = scalar(@$cells);
858: my $startcol = 1;
859: my @underlines; # Array of \cline cells if cellborder on.
860:
1.8 foxr 861:
1.11 foxr 862:
1.6 foxr 863: for (my $cell = 0; $cell < $cell_count; $cell++) {
864: my $contents = $cells->[$cell]->{'contents'};
1.7 foxr 865:
866: #
867: # Cell alignment is the default alignment unless
868: # explicitly specified in the cell.
869: # NOTE: at this point I don't know how to do vert alignment.
870: #
871:
872: my $halign = $def_halign;
873: if (defined ($cells->[$cell]->{'halign'})) {
874: $halign = $cells->[$cell]->{'halign'};
875: }
876:
877: # Create the horizontal alignment character:
878:
879: my $col_align = 'l';
1.8 foxr 880: my $embeddedAlignStart = "";
881: my $embeddedAlignEnd = "";
882:
1.7 foxr 883: if ($halign eq 'right') {
884: $col_align = 'r';
1.13 raeburn 885: $embeddedAlignStart = '\raggedleft';
1.7 foxr 886: }
887: if ($halign eq 'center') {
888: $col_align = 'c';
1.8 foxr 889: $embeddedAlignStart = '\begin{center}';
890: $embeddedAlignEnd = '\end{center}';
1.7 foxr 891: }
1.8 foxr 892:
893: # If the width has been specified, turn these into
894: # para mode; and wrap the contents in the start/stop stuff:
895:
896: if ($useP) {
1.14 ! foxr 897: my $cw;
! 898: if (defined($colwidths->{$cell})) {
! 899: $cw = $colwidths->{$cell};
! 900: } else {
! 901: $cw = $default_width;
! 902: }
! 903: my $cw = $cw * $cells->[$cell]->{'colspan'};
1.8 foxr 904: $col_align = "p{$cw $colunits}";
905: $contents = $embeddedAlignStart . $contents . $embeddedAlignEnd;
906: }
907:
1.7 foxr 908: if ($inner_border || ($outer_border && ($cell == 0))) {
909: $col_align = '|'.$col_align;
910: }
911: if ($inner_border || ($outer_border && ($cell == ($cell_count -1)))) {
912: $col_align = $col_align.'|';
913: }
914:
915: #factor in spans:
916:
1.6 foxr 917: my $cspan = $cells->[$cell]->{'colspan'};
918: my $nextcol = $startcol + $cspan;
1.8 foxr 919:
920: # If we can avoid the \multicolumn directive that's best as
921: # that makes some things like \parpic invalid in LaTeX which
922: # screws everything up.
923:
924: if (($cspan > 1) || !($col_align =~ /l/)) {
925:
926: $contents = '\multicolumn{'.$cspan.'}{'.$col_align.'}{'.$contents.'}';
927:
928: # A nasty edge case. If there's only one cell, the software will assume
929: # we're in complete control of the row so we need to end the row ourselves.
930:
931: if ($cell_count == 1) {
932: $contents .= ' \\\\';
933: }
934: }
1.6 foxr 935: if ($inner_border && ($cells->[$cell]->{'rowspan'} == 1)) {
936: my $lastcol = $nextcol -1;
937: push(@underlines, "\\cline{$startcol-$lastcol}");
938: }
939: $startcol = $nextcol;
940: # Rowspans should take care of themselves.
941:
942: push(@row, $contents);
943:
944: }
945: push(@data, \@row);
946: if ($inner_border) {
947: for (my $i =0; $i < scalar(@underlines); $i++) {
948: push(@data, [$underlines[$i]]);
949: }
950: }
951:
952: }
1.11 foxr 953: #
954: # Add bottom border if necessary: if the inner border was on, the loops above
955: # will have done a bottom line under the last cell.
956: #
957: if ($outer_border && !$inner_border) {
958: push(@data, ["\\cline{1-$column_count}"]);
959:
960: }
1.6 foxr 961: $table->set_data(\@data);
962:
963: my $coldef = "";
964: if ($outer_border || $inner_border) {
965: $coldef .= '|';
966: }
967: for (my $i =0; $i < $column_count; $i++) {
1.8 foxr 968: if ($useP) {
1.14 ! foxr 969: $coldef .= "p{$default_width $colunits}";
1.8 foxr 970: } else {
971: $coldef .= 'l';
972: }
1.6 foxr 973: if ($inner_border ||
974: ($outer_border && ($i == $column_count-1))) {
975: $coldef .= '|';
976: }
977: }
978: $table->{'coldef'} = $coldef;
979:
980: # Return the table:
981:
1.8 foxr 982: if ($tracing) { &Apache::lonnet::logthis("Leaving generate"); }
983:
1.12 foxr 984:
1.6 foxr 985: return $table;
986:
987: }
1.14 ! foxr 988: #---------------------------------------------------------------------------
! 989: #
! 990: # Private methods:
! 991: #
! 992:
! 993: #
! 994: # Convert size with units -> size in cm.
! 995: # The resulting size is floating point with no units so that it can be used in
! 996: # computation. Note that an illegal or missing unit is treated silently as
! 997: # cm for now.
! 998: #
! 999: sub size_to_cm {
! 1000: my ($this, $size_spec) = @_;
! 1001: my ($size, $units) = split(/ /, $size_spec);
! 1002: if (lc($units) eq 'mm') {
! 1003: return $size / 10.0;
! 1004: }
! 1005: if (lc($units) eq 'in') {
! 1006: return $size * 2.54;
! 1007: }
! 1008:
! 1009: return $size; # Default is cm.
! 1010: }
1.6 foxr 1011: #----------------------------------------------------------------------------
1.5 foxr 1012: # The following methods allow for testability.
1.4 foxr 1013:
1014:
1015: sub get_object_attribute {
1016: my ($self, $attribute) = @_;
1.8 foxr 1017: if ($tracing > 1) { &Apache::lonnet::logthis("get_object_attribute: $attribute"); }
1.4 foxr 1018: return $self->{$attribute};
1019: }
1020:
1.5 foxr 1021: sub get_row {
1022: my ($self, $row) = @_;
1.8 foxr 1023: if ($tracing > 1) { &Apache::lonnet::logthis("get_row"); }
1024:
1.5 foxr 1025: my $rows = $self->{'rows'}; # ref to an array....
1026: return $rows->[$row]; # ref to the row hash for the selected row.
1027: }
1.14 ! foxr 1028:
1.1 foxr 1029: # Mandatory initialization.
1.4 foxr 1030: BEGIN{
1031: }
1.1 foxr 1032:
1033: 1;
1034: __END__
1.14 ! foxr 1035:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>