diff --git a/lib/LaTeXML/Core/Alignment.pm b/lib/LaTeXML/Core/Alignment.pm index 54b05bca18..6b6458bf53 100644 --- a/lib/LaTeXML/Core/Alignment.pm +++ b/lib/LaTeXML/Core/Alignment.pm @@ -204,7 +204,6 @@ sub getColumnAfter { my ($self) = @_; my $column; if (($column = $self->currentColumn) && !$$column{omitted}) { - # Possible \@@eat@space ??? (if LaTeX style???) return Tokens(@{ $$column{after} }, T_CS('\@column@after')); } else { return Tokens(); } } @@ -329,7 +328,23 @@ sub beAbsorbed { # Normalize the border attribute my $border = join(' ', sort(map { split(/ */, $_) } $$cell{border} || '')); $border =~ s/(.) \1/$1$1/g; - my $empty = $$cell{empty} || !$$cell{boxes} || !scalar($$cell{boxes}->unlist); + my @classes = ($$cell{class}); + my $empty = $$cell{empty}; + my ($pre, $post); + # Heuristic: use common td CSS padding, or none, or explicit spacing within cell + # Ideally, we'd compare to a current \(tab|array)colsep + # and create a special CSS class for non-standard values + my $lpad = ($$cell{lspaces} ? $$cell{lspaces}->getWidth->valueOf : 0); + my $rpad = ($$cell{rspaces} ? $$cell{rspaces}->getWidth->valueOf : 0); + if ((!$empty || $$cell{boxes}) && ($lpad < Dimension('0.2em')->valueOf)) { + push(@classes, 'ltx_nopad_l') unless $ismath; } + elsif ($lpad < Dimension('1.5em')->valueOf) { } # do nothing + else { $pre = $$cell{lspaces}; } + if ((!$empty || $$cell{boxes}) && ($rpad < Dimension('0.2em')->valueOf)) { + push(@classes, 'ltx_nopad_r') unless $ismath; } + elsif ($rpad < Dimension('1.5em')->valueOf) { } # do nothing + else { $post = $$cell{rspaces}; } + my $class = join(' ', grep { $_; } @classes); $$cell{cell} = &{ $$self{openColumn} }($document, align => $$cell{align}, width => $$cell{width}, vattach => $$cell{vattach}, @@ -341,11 +356,14 @@ sub beAbsorbed { # Which properties do we expose to the constructor? x => $$cell{x}, y => $$cell{y}, cwidth => $$cell{cwidth}, cheight => $$cell{cheight}, cdepth => $$cell{cdepth}, + class => $class ); - if (!$empty) { + if (!$$cell{skippable}) { local $LaTeXML::BOX = $$cell{boxes}; $document->openElement('ltx:XMArg', rule => 'Anything,') if $ismath; # Hacky! + $document->absorb($pre) if $pre; $document->absorb($$cell{boxes}); + $document->absorb($post) if $post; $document->closeElement('ltx:XMArg') if $ismath; } &{ $$self{closeColumn} }($document); } @@ -406,33 +424,88 @@ sub normalize_cell_sizes { my ($self) = @_; # Examines: boxes, align, vattach # Sets: cwidth, cheight, cdepth (per cell) & empty + my $i = -1; foreach my $row (@{ $$self{rows} }) { + my $j = -1; + $i++; # Do we need to account for any space in the $$row{before} or $$row{after}? foreach my $cell (@{ $$row{columns} }) { + $j++; if (my $boxes = $$cell{boxes}) { my ($w, $h, $d, $cw, $ch, $cd) = $boxes->getSize(align => $$cell{align}, width => $$cell{width}, vattach => $$cell{vattach}); - Debug("CELL (" . join(',', map { $_ . "=" . ToString($$cell{$_}); } qw(align width vattach)) - . ") size " . showSize($w, $h, $d) - . " csize " . showSize($cw, $ch, $cd) - . " Boxes=" . ToString($boxes)) if $LaTeXML::DEBUG{halign} && $LaTeXML::DEBUG{size}; + my $fullw = $cw; + my ($lpad, $rpad, $padh, $padd); + if (my $lspaces = $$cell{lspaces}) { + ($lpad, $padh, $padd) = $lspaces->getSize; + $fullw = ($fullw ? $fullw->add($lpad) : $lpad) if $lpad; + $h = ($h ? $h->larger($padh) : $padh) if $padh; + $d = ($d ? $d->larger($padd) : $padd) if $padd; } + if (my $rspaces = $$cell{rspaces}) { + ($rpad, $padh, $padd) = $rspaces->getSize; + $fullw = ($fullw ? $fullw->add($rpad) : $rpad) if $rpad; + $h = ($h ? $h->larger($padh) : $padh) if $padh; + $d = ($d ? $d->larger($padd) : $padd) if $padd; } + my @boxes = $boxes->unlist; + my $isrule = scalar(@boxes) + && !(grep { !($_->getProperty('isHorizontalRule') || $_->getProperty('isVerticalRule') + || $_->getProperty('alignmentSkippable') + || (ref $_ eq 'LaTeXML::Core::Comment') + ); } @boxes); my $empty = - (((!$cw) || $cw->valueOf < 1) + (((!$fullw) || $fullw->valueOf < 1) && (((!$ch) || $ch->valueOf < 1) && ((!$cd) || $cd->valueOf < 1)) - || !(grep { !($_->getProperty('isSpace') || $_->getProperty('isHorizontalRule') || $_->getProperty('isVerticalRule')); } $boxes->unlist) + || $isrule ) && !preservedBoxes($boxes); - $$cell{cwidth} = $w || Dimension(0); - $$cell{cheight} = $h || Dimension(0); - $$cell{cdepth} = $d || Dimension(0); - $$cell{empty} = $empty; - $$cell{align} = undef if $empty; } + $$cell{cwidth} = $w || Dimension(0); + $$cell{cheight} = $h || Dimension(0); + $$cell{cdepth} = $d || Dimension(0); + $$cell{lpadding} = $lpad; + $$cell{rpadding} = $rpad; + $$cell{empty} = $empty; + $$cell{skippable} = $empty || isSkippable($boxes); + $$cell{align} = undef if $$cell{skippable}; + Debug("CELL[$i,$j] size=" . showSize($w, $h, $d) . " csize " . showSize($cw, $ch, $cd) + . ";\n " . join(',', map { $_ . "=" . ToString($$cell{$_}); } sort keys %$cell) + . "\n Boxes=" . Stringify($boxes) + ) if $LaTeXML::DEBUG{alignment_normalize}; + } else { - $$cell{empty} = 1; } + $$cell{empty} = 1; + $$cell{skippable} = 1; } } } return; } +# Check whether all these things are "empty" or only spaces or otherwise skippable in a table cell +# Very similar to IsEmpty, but also recognizes spaces or alignmentSkippable items +sub isSkippable { + my (@things) = @_; + foreach my $thing (@things) { + my $ref = ref $thing; + if (!$thing) { } + elsif ($ref eq 'LaTeXML::Core::Comment') { } + elsif ($ref eq 'LaTeXML::Core::Tokens') { + return 0 unless isSkippable($thing->unlist); } + elsif ($ref eq 'LaTeXML::Core::Token') { + my $cc = $$thing[1]; + return 0 if ($cc == CC_LETTER) || ($cc == CC_OTHER) || ($cc == CC_ACTIVE) || ($cc == CC_CS); } + elsif ((!$thing->getProperty('isEmpty')) + && (!$thing->getProperty('isSpace')) + && (!$thing->getProperty('alignmentSkippable'))) { + if ($ref eq 'LaTeXML::Core::Box') { + my $s = $thing->getString; + return 0 if (defined $s) && ($s !~ /^\s*$/); } + elsif ($ref eq 'LaTeXML::Core::List') { + return 0 unless isSkippable($thing->unlist); } + elsif ($ref eq 'LaTeXML::Core::Whatsit') { + if (my $body = $thing->getProperty('content_box')) { + return 0 unless isSkippable($body); } + else { + return 0; } } } } + return 1; } + sub showSize { my ($w, $h, $d) = @_; return '[' . ToString($w) . ' x ' . ToString($h) . ' + ' . ToString($d) . ']'; } @@ -535,7 +608,7 @@ sub normalize_sum_sizes { elsif ($a eq 'right') { $colx = $colx->add($dx); } } $$cell{x} = $colx; $$cell{y} = $rowpos[$i]; - Debug("CELL[$j,$i] " . showSize($$cell{cwidth}, $$cell{cheight}, $$cell{cdepth}) + Debug("CELL[$i,$j] " . showSize($$cell{cwidth}, $$cell{cheight}, $$cell{cdepth}) . " @ " . ToString($$cell{x}) . "," . ToString($$cell{y}) . " w/ " . join(',', map { $_ . '=' . ToString($$cell{$_}); } (qw(align vattach skipped colspan rowspan)))) @@ -580,7 +653,7 @@ sub normalize_mark_spans { if ($rrow) { for (my $jj = $j ; $jj < $j + $ncspan ; $jj++) { if (my $ccol = $$rrow{columns}[$jj]) { - if (!$$ccol{empty}) { + if (!$$ccol{skippable}) { $rowempty = 0; } } } } if (!$nrc) { } elsif (!$rrow || !$rowempty) { @@ -618,12 +691,26 @@ sub normalize_prune_rows { my @rows = @{ $$self{rows} }; my @filtered = (); for (my $i = 0 ; $i < scalar(@rows) ; $i++) { - my $row = $rows[$i]; - if (grep { !$$_{empty} } @{ $$row{columns} }) { # Not empty! so keep it + my $row = $rows[$i]; + my $next = $rows[$i + 1]; + # prunable if all cells are empty + # OR are only spacing NOT made visible by requiring both top & bottom borders + my $prunable = 1; + my $check_bracketting; + foreach my $c (@{ $$row{columns} }) { # Check if all cells are either empty or space only + $check_bracketting = 1 if $$c{skippable} && !$$c{empty}; + $prunable = 0 unless $$c{skippable}; } + if ($prunable && $check_bracketting # If spaces, make sure not bracketted by borders + && scalar(grep { ($$_{border} || '') =~ /t/i } @{ $$row{columns} })) { + if ($next) { + $prunable = 0 if scalar(grep { ($$_{border} || '') =~ /t/i } @{ $$next{columns} }); } + else { + $prunable = 0 if scalar(grep { ($$_{border} || '') =~ /b/i } @{ $$row{columns} }); } } + if (!$prunable) { push(@filtered, $row); } - elsif (my $next = $rows[$i + 1]) { # Remove empty row, but copy top border to NEXT row + elsif ($next) { # Remove empty row, but copy top border to NEXT row if ($preserve) { - push(@filtered, $row); next; } # don't remove inner rows from math EXCEPT last row!! + push(@filtered, $row); next; } # don't remove inner rows from math EXCEPT last row!! my $nc = scalar(@{ $$row{columns} }); my ($pruneh, $pruned) = (0, 0); for (my $j = 0 ; $j < $nc ; $j++) { @@ -638,7 +725,9 @@ sub normalize_prune_rows { $border =~ s/B/T/g; # but convert to top $$next{columns}[$j]{border} .= $border; } # add to NEXT row # This tpadding should be combined w/any extra rowspacing from \\[dim] ! - $$next{tpadding} = Dimension($pruneh + $pruned) if $pruneh + $pruned; } # And save padding. + $$next{tpadding} = Dimension($pruneh + $pruned) if $pruneh + $pruned; # And save padding. + Debug("PRUNE ROW $i") if $LaTeXML::DEBUG{alignment_normalize}; + } else { # Remove empty last row, but copy top border to bottom of prev. my $prev = $filtered[-1]; my $nc = scalar(@{ $$row{columns} }); @@ -657,7 +746,8 @@ sub normalize_prune_rows { if (defined $$ccol{rowspanned}) { # skip to spanning column if rowspanned! $ccol = $rows[$$ccol{rowspanned}]{columns}[$j]; } $$ccol{border} .= $border; } # add to PREVIOUS row. - $$prev{bpadding} = Dimension($pruneh + $pruned) if $pruneh + $pruned; } # And save padding. + $$prev{bpadding} = Dimension($pruneh + $pruned) if $pruneh + $pruned; # And save padding. + Debug("PRUNE ROW (last) $i") if $LaTeXML::DEBUG{alignment_normalize}; } } @rows = @filtered; $$self{rows} = [@filtered]; @@ -686,10 +776,13 @@ sub normalize_prune_columns { my $prev = $$row{columns}[$j - 1]; if (my $jj = $$prev{colspanned}) { $prev = $$row{columns}[$jj]; } - $border =~ s/[^rRlL]//g; # mask all but left border - $border =~ s/l/r/g; # convert to right - $border =~ s/L/R/g; # convert to right + $border =~ s/[^rRlL]//g; # mask all but left border + $border =~ s/l/r/g; # convert to right + $border =~ s/L/R/g; # convert to right $$prev{border} .= $border; + # Copy left spacing to right column, as well + $$prev{rspaces} = LaTeXML::Core::List::List($$prev{rspaces} || (), $$col{lspaces} || ()) if $$col{lspaces}; + $$prev{rpadding} = $$prev{rspaces}->getWidth if $$prev{rspaces}; if (my @preserve = preservedBoxes($$col{boxes})) { # Copy boxes over, in case side effects? $$prev{boxes} = LaTeXML::Core::List($$prev{boxes} ? ($$prev{boxes}->unlist, @preserve) : @preserve); } @@ -705,14 +798,16 @@ sub normalize_prune_columns { } # Now, remove the column $$row{columns} = [grep { $_ ne $col } @{ $$row{columns} }]; } } + $prunew = Dimension($prunew); if ($j) { # If not 1st row, add right padding to previous column foreach my $row (@rows) { if (my $col = $$row{columns}[$j - 1]) { - $$col{rpadding} = Dimension($prunew); } } } + $$col{rpadding} = ($$col{rpadding} ? $$col{rpadding}->add($prunew) : $prunew); } } } else { # Else add left padding to (newly) first column foreach my $row (@rows) { # And add the padding to previous column if (my $col = $$row{columns}[0]) { - $$col{lpadding} = Dimension($prunew); } } } + $$col{lpadding} = ($$col{lpadding} ? $$col{lpadding}->add($prunew) : $prunew); } } } + Debug("PRUNE COLUMN $j") if $LaTeXML::DEBUG{alignment_normalize}; } } } return; } @@ -769,6 +864,7 @@ sub ReadAlignmentTemplate { last unless $nopens; } push(@tokens, T_END); $LaTeXML::BUILD_TEMPLATE->setReversion(@tokens); + $LaTeXML::BUILD_TEMPLATE->finish; return $LaTeXML::BUILD_TEMPLATE; } sub parseAlignmentTemplate { diff --git a/lib/LaTeXML/Core/Alignment/Template.pm b/lib/LaTeXML/Core/Alignment/Template.pm index a828b29caf..16cd7d8895 100644 --- a/lib/LaTeXML/Core/Alignment/Template.pm +++ b/lib/LaTeXML/Core/Alignment/Template.pm @@ -53,11 +53,11 @@ sub addBeforeColumn { unshift(@{ $$self{save_before} }, @tokens); # NOTE: goes all the way to front! return; } -# NOTE: \@@eat@space should ONLY be added to LaTeX tabular style templates!!!! +# NOTE: \lx@column@trimright should ONLY be added to LaTeX tabular style templates!!!! # NOT \halign style templates! sub addAfterColumn { my ($self, @tokens) = @_; - $$self{current_column}{after} = Tokens(T_CS('\@@eat@space'), + $$self{current_column}{after} = Tokens(T_CS('\lx@column@trimright'), @tokens, @{ $$self{current_column}{after} }); return; } @@ -65,25 +65,38 @@ sub addAfterColumn { sub addBetweenColumn { my ($self, @tokens) = @_; my @cols = @{ $$self{columns} }; - if ($$self{current_column}) { + if (my $col = $$self{current_column}) { + if (!$$self{disabled_intercolumn}) { + unshift(@tokens, T_CS('\lx@intercol')); } $$self{current_column}{after} = Tokens(@{ $$self{current_column}{after} }, @tokens); } else { push(@{ $$self{save_between} }, @tokens); } return; } +sub disableIntercolumn { + my ($self) = @_; + if (my $col = $$self{current_column}) { + $$self{disabled_intercolumn} = 1; } + return; } + sub addColumn { my ($self, %properties) = @_; - my $col = {%properties}; + my $col = {%properties}; + if (my $prev = $$self{current_column}) { + $$prev{after} = Tokens(($$prev{after} || ()), T_CS('\lx@intercol')) + unless $$self{disabled_intercolumn}; } my @before = (); - push(@before, @{ $$self{save_between} }) if $$self{save_between}; + push(@before, @{ $$self{save_between} }) if $$self{save_between}; + push(@before, T_CS('\lx@intercol')) unless $$self{disabled_intercolumn}; + delete $$self{disabled_intercolumn}; + push(@before, $properties{before}->unlist) if $properties{before}; push(@before, @{ $$self{save_before} }) if $$self{save_before}; $$col{before} = Tokens(@before); my @after = (); - push(@after, T_CS('\@@eat@space')); + push(@after, T_CS('\lx@column@trimright')); push(@after, $properties{after}->unlist) if $properties{after}; - $$col{after} = Tokens(@after); -### $$col{after} = Tokens() unless $properties{after}; + $$col{after} = Tokens(@after); $$col{thead} = $properties{thead}; $$col{empty} = 1; $$self{save_between} = []; @@ -97,6 +110,13 @@ sub addColumn { push(@{ $$self{columns} }, $col); } return; } +sub finish { + my ($self) = @_; + if (my $prev = $$self{current_column}) { + $$prev{after} = Tokens(($$prev{after} || ()), T_CS('\lx@intercol')) + unless $$self{disabled_intercolumn}; } + return; } + # Methods for using a template. sub clone { my ($self) = @_; @@ -104,7 +124,7 @@ sub clone { foreach my $cell (@{ $$self{columns} }) { push(@dup, {%$cell}); } return bless { columns => [@dup], - repeated => $$self{repeated}, non_repeating => $$self{non_repeating}, + repeated => $$self{repeated}, non_repeating => $$self{non_repeating}, repeating => $$self{repeating} }, ref $self; } sub show { diff --git a/lib/LaTeXML/Package.pm b/lib/LaTeXML/Package.pm index 8d36279b09..d7a00a5f1d 100644 --- a/lib/LaTeXML/Package.pm +++ b/lib/LaTeXML/Package.pm @@ -974,9 +974,8 @@ sub TokenizeInternal { local $STATE = $STY_CATTABLE; return LaTeXML::Core::Mouth->new($string)->readTokens; } -# Check whether all these things are "empty" (spaces are empty!!) +# Check whether all these things are "empty" (spaces are NOT empty!!) # short-circuit: return 0 quickly if anything is NOT empty. -# Will we need to distinguish between "empty" and "not visible"?? sub IsEmpty { my (@things) = @_; foreach my $thing (@things) { @@ -988,16 +987,18 @@ sub IsEmpty { elsif ($ref eq 'LaTeXML::Core::Token') { my $cc = $$thing[1]; return 0 if ($cc == CC_LETTER) || ($cc == CC_OTHER) || ($cc == CC_ACTIVE) || ($cc == CC_CS); } - elsif ((!$thing->getProperty('isEmpty')) - && (!$thing->getProperty('isSpace'))) { # A space-like thing + elsif (!$thing->getProperty('isEmpty')) { if ($ref eq 'LaTeXML::Core::Box') { my $s = $thing->getString; - return 0 if (defined $s) && ($s !~ /^\s*$/); } + return 0 if (defined $s) && length($s); } elsif ($ref eq 'LaTeXML::Core::List') { return 0 unless IsEmpty($thing->unlist); } elsif ($ref eq 'LaTeXML::Core::Whatsit') { - return 0 unless ($thing->getDefinition eq $STATE->lookupDefinition(T_BEGIN)) - && IsEmpty($thing->getBody->unlist); } } } + # Sneaky Whatsit property for something (an arg) that stands-in for the whatsit's content. + if (my $body = $thing->getProperty('content_box')) { + return 0 unless IsEmpty($body); } + else { + return 0; } } } } return 1; } #====================================================================== diff --git a/lib/LaTeXML/Package/LaTeX.pool.ltxml b/lib/LaTeXML/Package/LaTeX.pool.ltxml index 5b21235ab8..94438288a8 100644 --- a/lib/LaTeXML/Package/LaTeX.pool.ltxml +++ b/lib/LaTeXML/Package/LaTeX.pool.ltxml @@ -2422,6 +2422,7 @@ sub eqnarrayBindings { properties => { preserve_structure => 1, attributes => {%attributes} })); Let("\\\\", '\@alignment@newline'); + Let('\lx@intercol', '\lx@math@intercol'); Let('\@row@before', '\eqnarray@row@before'); Let('\@row@after', '\eqnarray@row@after'); Let('\lx@eqnarray@save@label', '\label'); @@ -3647,11 +3648,14 @@ DefMacroI('\@tabbing@nexttab', undef, '\@tabbing@nexttab@marker&'); DefMacro('\@tabbing@newline OptionalMatch:* [Dimension]', '\@tabbing@newline@marker\cr'); DefMacroI('\@tabbing@kill', undef, '\@tabbing@kill@marker\cr\@tabbing@start@tabs'); -DefConstructorI('\@tabbing@tabset@marker', undef, '', reversion => '\='); -DefConstructorI('\@tabbing@nexttab@marker', undef, '', reversion => '\>'); +DefConstructorI('\@tabbing@tabset@marker', undef, '', reversion => '\=', + properties => { alignmentSkippable => 1 }); +DefConstructorI('\@tabbing@nexttab@marker', undef, '', reversion => '\>', + properties => { alignmentSkippable => 1 }); DefConstructorI('\@tabbing@newline@marker', undef, '', reversion => Tokens(T_CS("\\\\"), T_CR)); DefConstructorI('\@tabbing@kill@marker', undef, '', reversion => '\kill', - afterDigest => sub { LookupValue('Alignment')->removeRow; return; }); + afterDigest => sub { LookupValue('Alignment')->removeRow; return; }, + properties => { alignmentSkippable => 1 }); AssignValue(tabbing_start_tabs => Tokens()); DefMacroI('\@tabbing@start@tabs', undef, sub { LookupValue('tabbing_start_tabs')->unlist; }); @@ -3681,7 +3685,8 @@ DefMacro('\@tabbing@accent{}', sub { T_CS('\@tabbing@' . ToString($_[1])); }); sub tabbingBindings { AssignValue(Alignment => LaTeXML::Core::Alignment->new( template => LaTeXML::Core::Alignment::Template->new( - repeated => [{ after => Tokens(T_CS('\hfil')) }]), + repeated => [{ before => Tokens(T_CS('\lx@text@intercol')), + after => Tokens(T_CS('\hfil'), T_CS('\lx@text@intercol')) }]), openContainer => sub { $_[0]->openElement('ltx:tabular', @_[1 .. $#_]); }, closeContainer => sub { $_[0]->closeElement('ltx:tabular'); }, openRow => sub { $_[0]->openElement('ltx:tr', @_[1 .. $#_]); }, @@ -3757,6 +3762,7 @@ sub tabularBindings { $properties{strut} = LookupRegister('\baselineskip')->multiply(1.5); } # Account for html space alignmentBindings($template, 'text', %properties); Let("\\\\", '\@tabularcr'); + Let('\lx@intercol', '\lx@text@intercol'); Let('\tabularnewline', "\\\\"); # NOTE: Fit this back in!!!!!!! # # Do like AddToMacro, but NOT global! @@ -3873,7 +3879,8 @@ DefPrimitive('\@array@bindings [] AlignmentTemplate', sub { $$attr{rowsep} = Dimension(($str - 1) . 'em'); } alignmentBindings($template, 'math', attributes => $attr); MergeFont(mathstyle => 'text'); - Let("\\\\", '\@alignment@newline'); + Let("\\\\", '\@alignment@newline'); + Let('\lx@intercol', '\lx@math@intercol'); return; }); DefMacro('\array[]{}', diff --git a/lib/LaTeXML/Package/TeX.pool.ltxml b/lib/LaTeXML/Package/TeX.pool.ltxml index c0fbe5a98e..218a86e87b 100644 --- a/lib/LaTeXML/Package/TeX.pool.ltxml +++ b/lib/LaTeXML/Package/TeX.pool.ltxml @@ -2017,9 +2017,9 @@ DefParameterType('BoxSpecification', sub { reversion => sub { my ($spec) = @_; if (my $to = $spec && $spec->getValue('to')) { - return Tokens(Tokenize('to'), T_SPACE, Revert($to)); } + return Tokens(Tokenize('to'), Revert($to)); } elsif (my $spread = $spec && $spec->getValue('spread')) { - return Tokens(Tokenize('spread'), T_SPACE, Revert($spread)); } + return Tokens(Tokenize('spread'), Revert($spread)); } else { return; } }, optional => 1, undigested => 1); @@ -2202,6 +2202,7 @@ DefConstructor('\hbox BoxSpecification HBoxContents', sub { $whatsit->setWidth($w); } elsif (my $s = GetKeyVal($spec, 'spread')) { $whatsit->setWidth($box->getWidth->add($s)); } + $whatsit->setProperty(content_box => $box); return; }); # Cleanup foreignObjects: remove empty (or only
); and determine size @@ -2345,6 +2346,7 @@ DefConstructor('\vbox BoxSpecification VBoxContents', sub { $whatsit->setHeight($h); } elsif (my $s = GetKeyVal($spec, 'spread')) { $whatsit->setHeight($box->getHeight->add($s)); } + $whatsit->setProperty(content_box => $box); return; }); DefConstructor('\vtop BoxSpecification VBoxContents', sub { @@ -2361,6 +2363,7 @@ DefConstructor('\vtop BoxSpecification VBoxContents', sub { $whatsit->setHeight($h); } elsif (my $s = GetKeyVal($spec, 'spread')) { $whatsit->setHeight($box->getHeight->add($s)); } + $whatsit->setProperty(content_box => $box); return; }); DefParameterType('RuleSpecification', sub { @@ -2450,7 +2453,7 @@ DefConstructor('\hrule RuleSpecification', DefPrimitive('{', sub { my ($stomach) = @_; $stomach->bgroup; - my $open = Box(undef, undef, undef, T_BEGIN, isEmpty => 1); + my $open = Box(undef, undef, undef, T_BEGIN, isEmpty => 1, alignmentSkippable => 1); my $ismath = $STATE->lookupValue('IN_MATH'); my @body = $stomach->digestNextBody(); List($open, @body, mode => ($ismath ? 'math' : 'text')); }); @@ -2458,7 +2461,7 @@ DefPrimitive('{', sub { DefPrimitive('}', sub { my $f = LookupValue('font'); $_[0]->egroup; - Box(undef, $f, undef, T_END, isEmpty => 1); }); + Box(undef, $f, undef, T_END, isEmpty => 1, alignmentSkippable => 1); }); # These are for those screwy cases where you need to create a group like box, # more than just bgroup, egroup, @@ -2831,7 +2834,10 @@ DefColumnType('*{Number}{}', sub { DefColumnType('@{}', sub { my ($gullet, $filler) = @_; - $LaTeXML::BUILD_TEMPLATE->addBetweenColumn($filler->unlist); return; }); + $LaTeXML::BUILD_TEMPLATE->disableIntercolumn; + $LaTeXML::BUILD_TEMPLATE->addBetweenColumn($filler->unlist); + $LaTeXML::BUILD_TEMPLATE->disableIntercolumn; + return; }); #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # Alignment code @@ -2965,6 +2971,29 @@ DefConstructor('\@alignment@newline@markertall {Dimension}', '', DefMacroI('\tabularnewline', undef, '\cr'); # ??? +# \lx@intercol is our replacement for LaTeX's \@acol which places intercolumn space in tabular +# (but NOT used by TeX's \halign!) +DefMacro('\lx@intercol', ''); +# Candidates for binding \lx@intercol for LaTeX tabular or math arrays +# These provide "padding" of half tabcolsep, since added before & after columns +# [these could be \hskip\tabcolsep, but the expansion confounds trimColumnSpec] +DefConstructor('\lx@text@intercol', sub { + my ($document, %props) = @_; + $document->absorb(DimensionToSpaces($props{width})); }, + reversion => '\lx@intercol', + properties => sub { + my $defn; + my $w = (($defn = $STATE->lookupDefinition(T_CS('\tabcolsep'))) && $defn->isRegister + ? $defn->valueOf : Dimension(0)); + (width => $w, isSpace => 1); }); +DefConstructor('\lx@math@intercol', "", # mspace ??? + reversion => '\lx@intercol', + properties => sub { + my $defn; + my $w = (($defn = $STATE->lookupDefinition(T_CS('\arraycolsep'))) && $defn->isRegister + ? $defn->valueOf : Dimension(0)); + (width => $w, isSpace => 1); }); + #====================================================================== # Various decorations within alignments, rules, headers, etc @@ -3093,25 +3122,29 @@ sub translateAttachment { $pos = ($pos ? ToString($pos) : ''); return ($pos eq 't' ? 'top' : ($pos eq 'b' ? 'bottom' : 'middle')); } # undef meaning 'baseline' -# This removes trailing whitespace from the current digested list. -# It is useful as the 1st thing in the rhs template of things like {tabular}. +# This trims trailing whitespace from the current digested list, +# for use within latex tabular-style columns. # But note that \halign does NOT remove this trailing space! -DefPrimitiveI('\@@eat@space', undef, sub { +DefPrimitiveI('\lx@column@trimright', undef, sub { my $box; my @save = (); + my $s; while ($box = $LaTeXML::LIST[-1]) { if ($box->getProperty('alignmentSkippable') - || $box->getProperty('isFill')) { + || $box->getProperty('isFill') + || IsEmpty($box)) { push(@save, pop(@LaTeXML::LIST)); } - elsif (IsEmpty($box)) { - pop(@LaTeXML::LIST); } + elsif (ref $box eq 'LaTeXML::Core::List') { # Unwrap and continue + pop(@LaTeXML::LIST); + push(@LaTeXML::LIST, $box->unlist); } + elsif ((ref $box eq 'LaTeXML::Core::Box') + && defined($s = $box->getString) && ($s =~ /^\s*$/)) { + pop(@LaTeXML::LIST); } # remove any box containing only spaces else { last; } } push(@LaTeXML::LIST, @save); return; }); -#DefMacroI('\@@eat@space',undef,undef); - use constant T_hfil => T_CS('\hfil'); # Yet more special case hacking. Sometimes the order of tokens works for # TeX, but confuses us... In particular the order of $ and \hfil! @@ -3180,22 +3213,22 @@ sub parseHAlignTemplate { my ($gullet, $whatsit) = @_; my $t = $gullet->readNonSpace; Error('expected', '\bgroup', $gullet, "Missing \\halign box") unless $t->defined_as(T_BEGIN); - my $before = 1; # true if we're before a # in current column - my @pre = (); - my @post = (); - my @cols = (); - my $repeated = 0; - my @nonreps = (); - my $tabskip; # Need to use this somehow! - my @tokens = (); + my $before = 1; # true if we're before a # in current column + my @pre = (); + my @post = (); + my @cols = (); + my $repeated = 0; + my @nonreps = (); + my $tabskip = LookupRegister('\tabskip'); + my $nexttabskip = $tabskip; + my @tokens = (); ## Only expand certain things; See TeX book p.238 local $LaTeXML::ALIGN_STATE = 1000000; while ($t = $gullet->readToken) { my $cc = $t->getCatcode; if ($t->equals(T_CS('\tabskip'))) { # Read the tabskip assignment $gullet->readKeyword('='); - $tabskip = $gullet->readGlue; - push(@tokens, $t, $tabskip); } + $nexttabskip = $gullet->readGlue; } elsif ($t->equals(T_CS('\span'))) { # ex-span-ded next token. $gullet->unread($gullet->readXToken(0)); } elsif ($cc == CC_PARAM) { # Found the template's column slot @@ -3209,9 +3242,11 @@ sub parseHAlignTemplate { else { # Finished column spec; add it ## How should we be handling tabskip? An attribute on the cell or spacing? push(@cols, { - before => Tokens(beforeCellUnlist(Tokens(@pre))), - after => Tokens(afterCellUnlist(Tokens(@post))) }); - @pre = @post = (); $before = 1; } + tabskip => $tabskip, + before => Tokens(beforeCellUnlist(Tokens(@pre))), + after => Tokens(afterCellUnlist(Tokens(@post))) }); + $tabskip = $nexttabskip; + @pre = @post = (); $before = 1; } last unless $cc == CC_ALIGN; push(@tokens, $t); } elsif ($before) { # Other random tokens go into the column's pre-template @@ -3461,6 +3496,9 @@ sub extractAlignmentColumn { my @boxes = $boxes->unlist; my @saveleft = (); my @saveright = (); + my (@lspaces, @rspaces); + if (my $skip = $$colspec{tabskip}) { + push(@lspaces, Digest(Tokens(T_CS('\hskip'), $skip->revert, T_CS('\relax')))); } while (@boxes) { if (ref $boxes[0] eq 'LaTeXML::Core::List') { unshift(@boxes, shift(@boxes)->unlist); } @@ -3470,12 +3508,15 @@ sub extractAlignmentColumn { last; } elsif ($boxes[0]->getProperty('isVerticalRule')) { $border .= 'l'; + if (my $prev = $alignment->getColumn($n0 - 1)) { # space before | ? move to previous column + $$prev{rspaces} = List(($$prev{rspaces} || ()), @lspaces) if @lspaces; } + @lspaces = (); # then discard shift(@boxes); } + elsif ($boxes[0]->getProperty('isSpace')) { + push(@lspaces, shift(@boxes)); } elsif ($boxes[0]->getProperty('isHorizontalRule') || $boxes[0]->getProperty('alignmentSkippable') - || (ref $boxes[0] eq 'LaTeXML::Core::Comment') - || $boxes[0]->getProperty('isSpace') - || IsEmpty($boxes[0])) { + || (ref $boxes[0] eq 'LaTeXML::Core::Comment')) { push(@saveleft, shift(@boxes)); } else { last; } } @@ -3488,12 +3529,13 @@ sub extractAlignmentColumn { last; } elsif ($boxes[-1]->getProperty('isVerticalRule')) { $border .= 'r'; + @rspaces = (); # discard spacing after rule!!! (should save for next column?) pop(@boxes); } + elsif ($boxes[-1]->getProperty('isSpace')) { + unshift(@rspaces, pop(@boxes)); } elsif ($boxes[-1]->getProperty('isHorizontalRule') || $boxes[-1]->getProperty('alignmentSkippable') - || (ref $boxes[-1] eq 'LaTeXML::Core::Comment') - || $boxes[-1]->getProperty('isSpace') - || IsEmpty($boxes[-1])) { + || (ref $boxes[-1] eq 'LaTeXML::Core::Comment')) { unshift(@saveright, pop(@boxes)); } else { last; } } @@ -3505,7 +3547,10 @@ sub extractAlignmentColumn { $$colspec{align} = $align; $$colspec{border} = $border = ($$colspec{border} || '') . $border; $$colspec{boxes} = $boxes; + $$colspec{lspaces} = List(@lspaces) if @lspaces; + $$colspec{rspaces} = List(@rspaces) if @rspaces; $$colspec{colspan} = $n1 - $n0 + 1; + if ($$alignment{in_tabular_head} || $$alignment{in_tabular_foot}) { $$colspec{thead}{column} = 1; } for (my $i = $n0 + 1 ; $i <= $n1 ; $i++) { @@ -4360,6 +4405,7 @@ sub scriptHandler { # Parsing is too late! while (my $prev = pop(@LaTeXML::LIST)) { if (($prev->getProperty('isSpace')) + || ($prev->getProperty('isEmpty')) # EXPLICITLY empty, rather than {} || (ref $prev eq 'LaTeXML::Core::Comment')) { $prevspace = 1; # a space avoids double-scripts unshift(@putback, $prev); # put back? assuming it will add rpadding to previous??? @@ -6014,14 +6060,14 @@ DefMathI('\biguplus', undef, "\x{2A04}", # versus \x{228e} mathstyle => \&doVariablesizeOp); DefConstructorI('\limits', undef, '', afterDigest => sub { mergeLimits('mid'); }, - properties => { isSpace => 1 }); + properties => { isEmpty => 1 }); DefConstructorI('\nolimits', undef, '', afterDigest => sub { mergeLimits('post'); }, - properties => { isSpace => 1 }); + properties => { isEmpty => 1 }); DefConstructorI('\displaylimits', undef, '', afterDigest => sub { mergeLimits((($_[1]->getProperty('mathstyle') || '') eq 'display' ? 'mid' : 'post')); }, - properties => { isSpace => 1 }); + properties => { isEmpty => 1 }); sub mergeLimits { my ($pos) = @_; @@ -6765,6 +6811,7 @@ DefPrimitive('\lx@gen@matrix@bindings RequiredKeyVals:lx@GEN', sub { 'math', (keys %attributes ? (attributes => {%attributes}) : ())); # }); Let("\\\\", '\@alignment@newline'); + Let('\lx@intercol', '\lx@math@intercol'); Let('\@row@before', '\@empty'); # Disable special row treatment (eg. numbering) unless requested Let('\@row@after', '\@empty'); }); @@ -6889,11 +6936,12 @@ DefPrimitive('\lx@gen@cases@bindings RequiredKeyVals:lx@GEN', sub { { before => Tokens($style), after => Tokens(T_CS('\hfil')) }, { before => Tokens($style, ($condtext ? (T_CS('\lx@cases@condition')) : ())), - after => Tokens(T_CS('\@@eat@space'), + after => Tokens(T_CS('\lx@column@trimright'), ($condtext ? (T_CS('\lx@cases@end@condition')) : ()), T_CS('\hfil')) }]), 'math'); - Let("\\\\", '\@alignment@newline'); + Let("\\\\", '\@alignment@newline'); + Let('\lx@intercol', '\lx@math@intercol'); DefMacro('\@row@before', ''); # Don't inherit counter stepping from containing environments DefMacro('\@row@after', ''); }); diff --git a/lib/LaTeXML/Package/siunitx.sty.ltxml b/lib/LaTeXML/Package/siunitx.sty.ltxml index 1891f62cf2..e239690cf6 100644 --- a/lib/LaTeXML/Package/siunitx.sty.ltxml +++ b/lib/LaTeXML/Package/siunitx.sty.ltxml @@ -1466,7 +1466,7 @@ DefMacro('\lx@si@column@parse XUntil:\lx@si@column@end', sub { push(@pre, T_BEGIN, $p, T_END); } else { last; } } - pop(@tokens) if @tokens && Equals($tokens[-1], T_CS('\@@eat@space')); + pop(@tokens) if @tokens && Equals($tokens[-1], T_CS('\lx@column@trimright')); if (my $defns = six_convertUnits(Tokens(@tokens))) { $result = six_format_units(six_parse_units($defns)); } else { diff --git a/lib/LaTeXML/resources/CSS/LaTeXML.css b/lib/LaTeXML/resources/CSS/LaTeXML.css index 96e72f8b08..32dd24474e 100644 --- a/lib/LaTeXML/resources/CSS/LaTeXML.css +++ b/lib/LaTeXML/resources/CSS/LaTeXML.css @@ -358,6 +358,21 @@ span.ltx_rowspan { position:absolute; top:0; bottom:0; } .ltx_tabular .ltx_td, .ltx_tabular .ltx_th { padding:0.1em 0.5em; } +.ltx_tabular .ltx_td.ltx_nopad_l, +.ltx_tabular .ltx_th.ltx_nopad_l { padding-left:0; } +.ltx_tabular .ltx_td.ltx_nopad_r, +.ltx_tabular .ltx_th.ltx_nopad_r { padding-right:0; } + +/* min-height does NOT apply to tr! */ +.ltx_tabular .ltx_tr td:first-child::after, +.ltx_tabular .ltx_tr th:first-child::after { + content: ""; + display: inline-block; + vertical-align: top; + min-height: 1em; +} + + /* regular lines */ .ltx_border_t { border-top:1px solid black; } .ltx_border_r { border-right:1px solid black; } diff --git a/t/alignment/array.xml b/t/alignment/array.xml index 30fa034c84..3f915b98b8 100644 --- a/t/alignment/array.xml +++ b/t/alignment/array.xml @@ -46,11 +46,11 @@Cell long text with predefined width
Cell long text with predefined width
Cell long…
Cell long…
Second multilined Second multilined column head column head
-
+
-
+
Cell …
Cell …
Cell …
Cell …
Bad year.
Light trading due to a heavy winter.
No gnus was very good gnus this year.
Diagram | -Diagram |
+ | ||||
+ | - | + | ||||
| | |||||
| | |||||
+ | - | + | ||||
+ | - | + | ||||
| | |||||
| | |||||
+ | - | + | ||||
| | |||||
| | |||||
+ | - | + | ||||
| | |||||
| | |||||
+ | - | + | ||||
+ | - | + | ||||
| | |||||
|
- |
+ | ||||