Annotation of loncom/xml/lontable.pm, revision 1.16
1.1 foxr 1: # The LearningOnline Network with CAPA
2: # Generating TeX tables.
3: #
1.16 ! foxr 4: # $Id: lontable.pm,v 1.15 2010/11/18 17:12:14 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.
1.16 ! foxr 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
1.2 foxr 364:
365: =head3 Examples:
366:
1.4 foxr 367: my $cell_border = $table->cell_border(); # ask if cell borders are requested.
1.2 foxr 368: $table->cell_border(1); # Request cell borders.
369:
370: =cut
371:
1.4 foxr 372: sub cell_border {
1.2 foxr 373: my ($self, $new_value) = @_;
1.8 foxr 374: if($tracing) {&Apache::lonnet::logthis("cell_border: $new_value"); }
1.2 foxr 375: if (defined($new_value)) {
1.5 foxr 376: $self->{'inner_border'} = $new_value;
1.2 foxr 377: }
1.5 foxr 378: return $self->{'inner_border'};
1.2 foxr 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:
1.8 foxr 399: if($tracing) {&Apache::lonnet::logthis("caption: $new_value"); }
1.2 foxr 400: if (defined($new_value)) {
1.5 foxr 401: $self->{'caption'} = $new_value;
1.2 foxr 402: }
403:
1.5 foxr 404: return $self->{'caption'};
1.2 foxr 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:
1.1 foxr 416:
1.2 foxr 417: my $theme = $table->theme();
418: $table->theme("Dresden");
419:
420: =cut
421:
422: sub theme {
423: my ($self, $new_value) = @_;
1.8 foxr 424: if($tracing) {&Apache::lonnet::logthis("theme $new_value"); }
1.2 foxr 425: if (defined($new_value)) {
1.5 foxr 426: $self->{'theme'} = $new_value;
1.2 foxr 427: }
1.5 foxr 428: return $self->{'theme'};
1.2 foxr 429: }
430:
431: =pod
432:
1.7 foxr 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) = @_;
1.8 foxr 444: if($tracing) {&Apache::lonnet::logthis("width = $new_value"); }
445:
1.7 foxr 446: if (defined($new_value)) {
447: $self->{'width'} = $new_value;
448: }
449: return $self->{'width'}; # Could be undef.
450: }
451:
452: =pod
453:
1.2 foxr 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 {
1.5 foxr 481: my ($self, $config) = @_;
1.8 foxr 482: if($tracing) {&Apache::lonnet::logthis("start_row"); }
1.5 foxr 483: if ($self->{'row_open'}) {
1.4 foxr 484: $self->end_row();
1.2 foxr 485: }
486: my $row_hash = {
487: default_halign => "left",
488: default_valign => "top",
1.6 foxr 489: cell_width => 0,
1.2 foxr 490: cells => []
491: };
492:
493: # Override the defaults if the config hash is present:
494:
1.5 foxr 495: if (defined($config)) {
496: foreach my $key (keys %$config) {
497: $row_hash->{$key} = $config->{$key};
1.2 foxr 498: }
499: }
1.5 foxr 500:
1.2 foxr 501:
1.5 foxr 502: my $rows = $self->{'rows'};
1.2 foxr 503: push(@$rows, $row_hash);
504:
1.5 foxr 505: $self->{"row_open"} = 1; # Row is now open and ready for business.
1.2 foxr 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:
1.4 foxr 516: $table->end_row();
1.2 foxr 517:
518:
519: =cut
520:
1.4 foxr 521: sub end_row {
1.2 foxr 522: my ($self) = @_;
1.8 foxr 523: if($tracing) {&Apache::lonnet::logthis("end_row"); }
1.5 foxr 524: if ($self->{'row_open'}) {
1.2 foxr 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:
1.6 foxr 529: my $row = $self->{'rows'}->[-1];
1.5 foxr 530: my $cells = $row->{'cells'};
1.3 foxr 531:
1.6 foxr 532: if ($row->{'cell_width'} > $self->{'column_count'}) {
533: $self->{'column_count'} = $row->{'cell_width'};
1.2 foxr 534: }
535:
1.5 foxr 536: $self->{'row_open'} = 0;;
1.2 foxr 537: }
538: }
539:
540: =pod
541:
1.3 foxr 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:
1.2 foxr 547:
1.3 foxr 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:
1.2 foxr 551:
1.3 foxr 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:
1.14 foxr 565:
1.3 foxr 566: =back
1.2 foxr 567:
568: =cut
569:
1.3 foxr 570: sub configure_row {
571: my ($self, $config) = @_;
1.8 foxr 572: if($tracing) {&Apache::lonnet::logthis("configure_row");}
1.5 foxr 573: if (!$self->{'row_open'}) {
1.3 foxr 574: $self->start_row();
575: }
576:
1.5 foxr 577: my $row = $self->{'rows'}[-1];
1.3 foxr 578: foreach my $config_item (keys %$config) {
579: $row->{$config_item} = $config->{$config_item};
580: }
1.2 foxr 581: }
1.1 foxr 582:
583:
1.3 foxr 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:
1.14 foxr 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:
1.3 foxr 630: =back
631:
632: =cut
633:
634: sub add_cell {
635: my ($self, $text, $config) = @_;
636:
1.8 foxr 637: if($tracing) {&Apache::lonnet::logthis("add_cell : $text"); }
638:
1.3 foxr 639: # If a row is not open, we must open it:
640:
1.5 foxr 641: if (!$self->{'row_open'}) {
1.3 foxr 642: $self->start_row();
643: }
1.6 foxr 644: my $rows = $self->{'rows'};
645: my $current_row = $rows->[-1];
1.5 foxr 646: my $current_cells = $current_row->{'cells'};
1.6 foxr 647: my $last_coord = $current_row->{'cell_width'};
1.3 foxr 648:
1.6 foxr 649: # We have to worry about row spans if there is a prior row:
1.3 foxr 650:
1.6 foxr 651: if (scalar(@$rows) > 1) {
1.3 foxr 652:
1.6 foxr 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];
1.8 foxr 665: if (!defined($prior_cell)) {
666: last;
667: }
1.6 foxr 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: }
1.3 foxr 685: }
1.6 foxr 686:
1.3 foxr 687: }
1.6 foxr 688:
1.3 foxr 689: #
690: # Now we're ready to build up our cell:
691:
692: my $cell = {
693: rowspan => 1,
694: colspan => 1,
1.6 foxr 695: start_col => $last_coord,
1.3 foxr 696: contents => $text
697: };
698:
699: if (defined($config)) {
700: foreach my $key (keys(%$config)) {
1.15 raeburn 701: if ($key eq 'colspan') {
702: next if ($config->{$key} == 0);
703: }
1.3 foxr 704: $cell->{$key} = $config->{$key};
705: }
706: }
1.14 foxr 707:
1.6 foxr 708: $current_row->{'cell_width'} += $cell->{'colspan'};
709:
1.14 foxr 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:
1.3 foxr 735: push(@$current_cells, $cell);
1.8 foxr 736:
737: if ($tracing) { &Apache::lonnet::logthis("add_cell done"); }
1.3 foxr 738: }
739:
1.8 foxr 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:
1.6 foxr 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) = @_;
1.8 foxr 779: my $useP = 0;
1.14 foxr 780:
781: my $colunits = 'cm'; # All widths get normalized to cm.
782: my $tablewidth;
1.6 foxr 783:
1.8 foxr 784: if($tracing) {&Apache::lonnet::logthis("generate"); }
1.12 foxr 785: my $table = Apache::lonlatextable->new();
786:
1.8 foxr 787:
1.7 foxr 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:
1.14 foxr 796: my $default_width;
797: my $colwidths = $this->{'col_widths'};
1.7 foxr 798: if (defined ($this->{'width'})) {
1.14 foxr 799: $tablewidth = $this->{'width'};
800: $tablewidth = $this->size_to_cm($tablewidth);
801:
1.8 foxr 802: $useP = 1;
803:
1.14 foxr 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: }
1.7 foxr 849: }
850:
1.14 foxr 851:
852:
853:
1.6 foxr 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:
1.16 ! foxr 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:
1.11 foxr 866: # Add a top line if the outer or inner border is enabled:
867:
1.16 ! foxr 868: if ($outer_border || $cell_ul_border) {
1.11 foxr 869: push(@data, ["\\cline{1-$column_count}"]);
870:
871: }
872:
1.6 foxr 873: for (my $row = 0; $row < $row_count; $row++) {
874: my @row;
875: my $cells = $rows->[$row]->{'cells'};
1.7 foxr 876: my $def_halign = $rows->[$row]->{'default_halign'};
1.6 foxr 877: my $cell_count = scalar(@$cells);
878: my $startcol = 1;
879: my @underlines; # Array of \cline cells if cellborder on.
880:
1.8 foxr 881:
1.11 foxr 882:
1.6 foxr 883: for (my $cell = 0; $cell < $cell_count; $cell++) {
884: my $contents = $cells->[$cell]->{'contents'};
1.7 foxr 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';
1.8 foxr 900: my $embeddedAlignStart = "";
901: my $embeddedAlignEnd = "";
902:
1.7 foxr 903: if ($halign eq 'right') {
904: $col_align = 'r';
1.13 raeburn 905: $embeddedAlignStart = '\raggedleft';
1.7 foxr 906: }
907: if ($halign eq 'center') {
908: $col_align = 'c';
1.8 foxr 909: $embeddedAlignStart = '\begin{center}';
910: $embeddedAlignEnd = '\end{center}';
1.7 foxr 911: }
1.8 foxr 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) {
1.14 foxr 917: my $cw;
918: if (defined($colwidths->{$cell})) {
919: $cw = $colwidths->{$cell};
920: } else {
921: $cw = $default_width;
922: }
1.15 raeburn 923: $cw = $cw * $cells->[$cell]->{'colspan'};
1.8 foxr 924: $col_align = "p{$cw $colunits}";
925: $contents = $embeddedAlignStart . $contents . $embeddedAlignEnd;
926: }
927:
1.16 ! foxr 928: if ($cell_lr_border || ($outer_border && ($cell == 0))) {
1.7 foxr 929: $col_align = '|'.$col_align;
930: }
1.16 ! foxr 931: if ($cell_lr_border || ($outer_border && ($cell == ($cell_count -1)))) {
1.7 foxr 932: $col_align = $col_align.'|';
933: }
934:
935: #factor in spans:
936:
1.6 foxr 937: my $cspan = $cells->[$cell]->{'colspan'};
938: my $nextcol = $startcol + $cspan;
1.8 foxr 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: }
1.16 ! foxr 955: if ($cell_ul_border && ($cells->[$cell]->{'rowspan'} == 1)) {
1.6 foxr 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);
1.16 ! foxr 966: if ($cell_ul_border) {
1.6 foxr 967: for (my $i =0; $i < scalar(@underlines); $i++) {
968: push(@data, [$underlines[$i]]);
969: }
970: }
971:
972: }
1.11 foxr 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: #
1.16 ! foxr 977: if ($outer_border && !$cell_ul_border) {
1.11 foxr 978: push(@data, ["\\cline{1-$column_count}"]);
979:
980: }
1.6 foxr 981: $table->set_data(\@data);
982:
983: my $coldef = "";
1.16 ! foxr 984: if ($outer_border || $cell_lr_border) {
1.6 foxr 985: $coldef .= '|';
986: }
987: for (my $i =0; $i < $column_count; $i++) {
1.8 foxr 988: if ($useP) {
1.14 foxr 989: $coldef .= "p{$default_width $colunits}";
1.8 foxr 990: } else {
991: $coldef .= 'l';
992: }
1.16 ! foxr 993: if ($cell_lr_border ||
1.6 foxr 994: ($outer_border && ($i == $column_count-1))) {
995: $coldef .= '|';
996: }
997: }
998: $table->{'coldef'} = $coldef;
999:
1000: # Return the table:
1001:
1.8 foxr 1002: if ($tracing) { &Apache::lonnet::logthis("Leaving generate"); }
1003:
1.12 foxr 1004:
1.6 foxr 1005: return $table;
1006:
1007: }
1.14 foxr 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: }
1.6 foxr 1031: #----------------------------------------------------------------------------
1.5 foxr 1032: # The following methods allow for testability.
1.4 foxr 1033:
1034:
1035: sub get_object_attribute {
1036: my ($self, $attribute) = @_;
1.8 foxr 1037: if ($tracing > 1) { &Apache::lonnet::logthis("get_object_attribute: $attribute"); }
1.4 foxr 1038: return $self->{$attribute};
1039: }
1040:
1.5 foxr 1041: sub get_row {
1042: my ($self, $row) = @_;
1.8 foxr 1043: if ($tracing > 1) { &Apache::lonnet::logthis("get_row"); }
1044:
1.5 foxr 1045: my $rows = $self->{'rows'}; # ref to an array....
1046: return $rows->[$row]; # ref to the row hash for the selected row.
1047: }
1.14 foxr 1048:
1.1 foxr 1049: # Mandatory initialization.
1.4 foxr 1050: BEGIN{
1051: }
1.1 foxr 1052:
1053: 1;
1054: __END__
1.14 foxr 1055:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>