From 8445224c4cff66b3f3516116b85a7bda53af66d1 Mon Sep 17 00:00:00 2001 From: Deyan Ginev Date: Wed, 11 Mar 2026 16:08:16 -0400 Subject: [PATCH 1/2] fix alignment-token early return inside nested boxing groups digestNextBody() was prematurely returning on \cr/\lx@hidden@cr tokens even when deep inside nested hbox/vbox groups within a tabular cell. This caused cascading errors when \adjustbox{valign=t}{\begin{subfigure}...} was used inside \tabular, as the \\ inside subfigure leaked out as \lx@hidden@cr and triggered the alignment early-return check. Fix: guard the alignment-token check with a boxing-depth comparison so it only fires at the original alignment nesting level, not inside deeper boxing groups. Also includes minor whitespace reformatting by latexmllint. Co-Authored-By: Claude Opus 4.6 --- lib/LaTeXML/Core/Stomach.pm | 128 ++++++++++++++++++------------------ 1 file changed, 65 insertions(+), 63 deletions(-) diff --git a/lib/LaTeXML/Core/Stomach.pm b/lib/LaTeXML/Core/Stomach.pm index 27ccf3158..b199fc34d 100644 --- a/lib/LaTeXML/Core/Stomach.pm +++ b/lib/LaTeXML/Core/Stomach.pm @@ -100,7 +100,9 @@ sub digestNextBody { my @aug = (); while (defined($token = $$self{gullet}->getPendingComment || $$self{gullet}->readXToken(1))) { - if ($alignment && scalar(@LaTeXML::LIST) && (Equals($token, T_ALIGN) || + if ($alignment && scalar(@LaTeXML::LIST) + && (scalar(@{ $$self{boxing} }) <= $initdepth) # Only at alignment nesting level + && (Equals($token, T_ALIGN) || Equals($token, T_CS('\cr')) || Equals($token, T_CS('\lx@hidden@cr')) || Equals($token, T_CS('\lx@hidden@crcr')))) { # at least \over calls in here without the intent to passing through the alignment. @@ -231,13 +233,13 @@ sub invokeToken_simple { my $cc = $meaning->getCatcode; my $font = $STATE->lookupValue('font'); if ($cc == CC_SPACE) { - $STATE->clearPrefixes; # prefixes shouldn't apply here. - if($STATE->lookupValue('MODE') =~ /(?:math|vertical)$/) { + $STATE->clearPrefixes; # prefixes shouldn't apply here. + if ($STATE->lookupValue('MODE') =~ /(?:math|vertical)$/) { return (); } else { enterHorizontal($self); return Box($meaning->toString, $font, $$self{gullet}->getLocator, $meaning); } } - elsif ($cc == CC_COMMENT) { # Note: Comments need char decoding as well! + elsif ($cc == CC_COMMENT) { # Note: Comments need char decoding as well! my $comment = LaTeXML::Package::FontDecodeString($meaning->toString, undef, 1); # However, spaces normally would have be digested away as positioning... my $badspace = pack('U', 0xA0) . "\x{0335}"; # This is at space's pos in OT1 @@ -331,16 +333,16 @@ sub bgroup { sub egroup { my ($self) = @_; - if ($STATE->isValueBound('BOUND_MODE', 0)) { # Last stack frame was a mode switch!?!?! - # Don't pop if there's an error; maybe we'll recover? + if ($STATE->isValueBound('BOUND_MODE', 0)) { # Last stack frame was a mode switch!?!?! + # Don't pop if there's an error; maybe we'll recover? Error('unexpected', $LaTeXML::CURRENT_TOKEN, $self, - "Attempt to close a group that switched to mode ".$STATE->lookupValue('MODE'), + "Attempt to close a group that switched to mode " . $STATE->lookupValue('MODE'), currentFrameMessage($self)); } - elsif ($STATE->lookupValue('groupNonBoxing')) { # or group was opened with \begingroup + elsif ($STATE->lookupValue('groupNonBoxing')) { # or group was opened with \begingroup Error('unexpected', $LaTeXML::CURRENT_TOKEN, $self, "Attempt to close boxing group", currentFrameMessage($self)); } - else { # Don't pop if there's an error; maybe we'll recover? + else { # Don't pop if there's an error; maybe we'll recover? popStackFrame($self, 0); } return; } @@ -351,16 +353,16 @@ sub begingroup { sub endgroup { my ($self) = @_; - if ($STATE->isValueBound('BOUND_MODE', 0)) { # Last stack frame was a mode switch!?!?! - # Don't pop if there's an error; maybe we'll recover? + if ($STATE->isValueBound('BOUND_MODE', 0)) { # Last stack frame was a mode switch!?!?! + # Don't pop if there's an error; maybe we'll recover? Error('unexpected', $LaTeXML::CURRENT_TOKEN, $self, - "Attempt to close a group that switched to mode ".$STATE->lookupValue('MODE'), + "Attempt to close a group that switched to mode " . $STATE->lookupValue('MODE'), currentFrameMessage($self)); } elsif (!$STATE->lookupValue('groupNonBoxing')) { # or group was opened with \bgroup Error('unexpected', $LaTeXML::CURRENT_TOKEN, $self, "Attempt to close non-boxing group", currentFrameMessage($self)); } - else { # Don't pop if there's an error; maybe we'll recover? + else { # Don't pop if there's an error; maybe we'll recover? popStackFrame($self, 1); } return; } @@ -396,41 +398,41 @@ sub endgroup { #---------------------------------------------------------------------- # These are the only modes that you can beginMode|endMode, and must be entered that way. our %bindable_mode = ( - text => 'restricted_horizontal', - restricted_horizontal => 'restricted_horizontal', - vertical => 'internal_vertical', - internal_vertical => 'internal_vertical', - math => 'math', - inline_math => 'math', - display_math => 'display_math'); + text => 'restricted_horizontal', + restricted_horizontal => 'restricted_horizontal', + vertical => 'internal_vertical', + internal_vertical => 'internal_vertical', + math => 'math', + inline_math => 'math', + display_math => 'display_math'); # Switch to horizontal mode, w/o stacking the mode # Can really only switch to horizontal mode from vertical|internal_vertical, # so no math, font, etc changes are needed. sub enterHorizontal { - my($self) = @_; - my $mode = $STATE->lookupValue('MODE'); - if($mode =~ /vertical$/){ - Debug("MODE enter horizontal, from $mode, for ".Stringify($LaTeXML::CURRENT_TOKEN)) - if $LaTeXML::DEBUG{modes}; - $STATE->assignValue(MODE => 'horizontal', 'inplace'); } # SAME frame as BOUND_MODE! - elsif (($mode =~ /horizontal$/) || ($mode =~ /math$/)) { } # ignorable? + my ($self) = @_; + my $mode = $STATE->lookupValue('MODE'); + if ($mode =~ /vertical$/) { + Debug("MODE enter horizontal, from $mode, for " . Stringify($LaTeXML::CURRENT_TOKEN)) + if $LaTeXML::DEBUG{modes}; + $STATE->assignValue(MODE => 'horizontal', 'inplace'); } # SAME frame as BOUND_MODE! + elsif (($mode =~ /horizontal$/) || ($mode =~ /math$/)) { } # ignorable? else { - Warn('unexpected',$mode,$self, + Warn('unexpected', $mode, $self, "Cannot switch to horizontal mode from $mode"); } return; } # Resume vertical mode, if in horizontal mode, by executing \par, in TeX-like fashion. sub leaveHorizontal { - my($self) = @_; - my $mode = $STATE->lookupValue('MODE'); - my $bound = $STATE->lookupValue('BOUND_MODE'); + my ($self) = @_; + my $mode = $STATE->lookupValue('MODE'); + my $bound = $STATE->lookupValue('BOUND_MODE'); # This needs to be an invisible, and slightly gentler, \par (see \lx@normal@par) # BUT still allow user defined \par ! if (($mode eq 'horizontal') && ($bound =~ /vertical$/)) { local $LaTeXML::INTERNAL_PAR = 1; - Debug("MODE leaving $mode via \\par (within $bound), for ".Stringify($LaTeXML::CURRENT_TOKEN)) - if $LaTeXML::DEBUG{modes}; + Debug("MODE leaving $mode via \\par (within $bound), for " . Stringify($LaTeXML::CURRENT_TOKEN)) + if $LaTeXML::DEBUG{modes}; push(@LaTeXML::LIST, $self->invokeToken(T_CS('\par'))); } return; } @@ -438,35 +440,35 @@ sub leaveHorizontal { # Note that TeX would have done paragraph line-breaking, resulting in essentially # a vertical list. sub repackHorizontal { - my($self)=@_; + my ($self) = @_; my @para = (); my $item; my $mode; my $keep = 0; - while(@LaTeXML::LIST - && ($item = $LaTeXML::LIST[-1]) - && (($mode = ($item->getProperty('mode')||'horizontal')) - =~ /^(?:horizontal|restricted_horizontal|math)$/)) { + while (@LaTeXML::LIST + && ($item = $LaTeXML::LIST[-1]) + && (($mode = ($item->getProperty('mode') || 'horizontal')) + =~ /^(?:horizontal|restricted_horizontal|math)$/)) { # if ONLY horizontal mode spaces, we can prune them; it just makes an empty ltx:p - $keep = 1 if ($mode ne 'horizontal') || ! $item->getProperty('isSpace'); - unshift(@para,pop(@LaTeXML::LIST)); } - push(@LaTeXML::LIST, List(@para, mode=>'horizontal')) if $keep; + $keep = 1 if ($mode ne 'horizontal') || !$item->getProperty('isSpace'); + unshift(@para, pop(@LaTeXML::LIST)); } + push(@LaTeXML::LIST, List(@para, mode => 'horizontal')) if $keep; return; } # Resume vertical mode, internal form: reset mode, and repacks recently # digested horizontal items. This is useful within argument digestion, eg. sub leaveHorizontal_internal { - my($self) = @_; - my $mode = $STATE->lookupValue('MODE'); - my $bound = $STATE->lookupValue('BOUND_MODE'); + my ($self) = @_; + my $mode = $STATE->lookupValue('MODE'); + my $bound = $STATE->lookupValue('BOUND_MODE'); # This needs to be an invisible, and slightly gentler, \par (see \lx@normal@par) # BUT still allow user defined \par ! if (($mode eq 'horizontal') && ($bound =~ /vertical$/)) { - Debug("MODE leave $mode, resuming $bound, for ".Stringify($LaTeXML::CURRENT_TOKEN)) - if $LaTeXML::DEBUG{modes}; + Debug("MODE leave $mode, resuming $bound, for " . Stringify($LaTeXML::CURRENT_TOKEN)) + if $LaTeXML::DEBUG{modes}; repackHorizontal($self); $STATE->assignValue(MODE => $bound, 'inplace'); } - return; } + return; } # Mode switch to $umode; generally pushes a new stack frame, sets various state variables # In RARE cases, we need to do this WITHOUT a new stack frome (eg. \begin{document}) @@ -476,16 +478,16 @@ sub beginMode { if (my $mode = $bindable_mode{$umode}) { my $prevmode = $STATE->lookupValue('MODE'); my $prevbound = $STATE->lookupValue('BOUND_MODE'); - my $ismath = $mode =~ /math$/; + my $ismath = $mode =~ /math$/; my $wasmath = $prevmode =~ /math$/; - pushStackFrame($self) unless $noframe; # Effectively bgroup - $STATE->assignValue(BOUND_MODE => $mode, 'local'); # New value within this frame! + pushStackFrame($self) unless $noframe; # Effectively bgroup + $STATE->assignValue(BOUND_MODE => $mode, 'local'); # New value within this frame! $STATE->assignValue(MODE => $mode, 'local'); $STATE->assignValue(IN_MATH => $ismath, 'local'); Debug("MODE bind $mode, from $prevmode " - .($prevbound eq $prevmode ? '' : "(in $prevbound) ") - .", for ".Stringify($LaTeXML::CURRENT_TOKEN)) - if $LaTeXML::DEBUG{modes}; + . ($prevbound eq $prevmode ? '' : "(in $prevbound) ") + . ", for " . Stringify($LaTeXML::CURRENT_TOKEN)) + if $LaTeXML::DEBUG{modes}; my $curfont = $STATE->lookupValue('font'); if ($mode eq $prevbound) { } elsif ($ismath) { @@ -505,7 +507,7 @@ sub beginMode { my $ereg = $STATE->lookupDefinition($every); if (my $toks = $ereg && $ereg->isRegister && $ereg->valueOf()) { $self->getGullet->unread($toks); } } - elsif($wasmath) { + elsif ($wasmath) { # When entering text mode, we should set the font to the text font in use before the math # but inherit color and size $STATE->assignValue(font => $STATE->lookupValue('savedfont')->merge( @@ -513,7 +515,7 @@ sub beginMode { size => $curfont->getSize), 'local'); } } else { - Warn('unexpected',$mode,$self, "Cannot enter $mode mode"); } + Warn('unexpected', $mode, $self, "Cannot enter $mode mode"); } return; } # End the mode $umode; generally pops the stack frome. @@ -524,20 +526,20 @@ sub endMode { if (my $mode = $bindable_mode{$umode}) { if ((!$STATE->isValueBound('BOUND_MODE', 0)) # Last stack frame was NOT a mode switch!?!?! || ($STATE->lookupValue('BOUND_MODE') ne $mode)) { # Or was a mode switch to a different mode - # Don't pop if there's an error; maybe we'll recover? + # Don't pop if there's an error; maybe we'll recover? Error('unexpected', $LaTeXML::CURRENT_TOKEN, $self, "Attempt to end mode $mode", currentFrameMessage($self)); } else { - leaveHorizontal_internal($self) if $mode =~ /vertical$/; # nopar version! + leaveHorizontal_internal($self) if $mode =~ /vertical$/; # nopar version! if ($noframe) { - $self->executeBeforeAfterGroup; } # No pop, but at least, do beforeAfterGrup + $self->executeBeforeAfterGroup; } # No pop, but at least, do beforeAfterGrup else { - popStackFrame($self); } # Effectively egroup. - Debug("MODE unbind $mode, resume ".$STATE->lookupValue('MODE').", for ".Stringify($LaTeXML::CURRENT_TOKEN)) - if $LaTeXML::DEBUG{modes}; - }} + popStackFrame($self); } # Effectively egroup. + Debug("MODE unbind $mode, resume " . $STATE->lookupValue('MODE') . ", for " . Stringify($LaTeXML::CURRENT_TOKEN)) + if $LaTeXML::DEBUG{modes}; + } } else { - Warn('unexpected',$mode,$self, "Cannot end $mode mode"); } + Warn('unexpected', $mode, $self, "Cannot end $mode mode"); } return; } #********************************************************************** From 7159746719a5731f7193be1473cd8dbad89d0730 Mon Sep 17 00:00:00 2001 From: Deyan Ginev Date: Tue, 17 Mar 2026 09:26:02 -0400 Subject: [PATCH 2/2] add frameDepth guard to readBoxContents scanner; rebind \\ in beforeFloat --- lib/LaTeXML/Engine/TeX_Box.pool.ltxml | 204 +++++++++--------- .../Engine/latex_constructs.pool.ltxml | 1 + 2 files changed, 103 insertions(+), 102 deletions(-) diff --git a/lib/LaTeXML/Engine/TeX_Box.pool.ltxml b/lib/LaTeXML/Engine/TeX_Box.pool.ltxml index 1f9aa334b..2de77264e 100644 --- a/lib/LaTeXML/Engine/TeX_Box.pool.ltxml +++ b/lib/LaTeXML/Engine/TeX_Box.pool.ltxml @@ -15,9 +15,9 @@ use strict; use warnings; use LaTeXML::Package; -DebuggableFeature('svg', "Debug SVG generation"); +DebuggableFeature('svg', "Debug SVG generation"); DebuggableFeature('svg_verbose', "Debug SVG generation, verbosely"); -$LaTeXML::DEBUG{svg}=1 if $$LaTeXML::DEBUG{svg_verbose}; +$LaTeXML::DEBUG{svg} = 1 if $$LaTeXML::DEBUG{svg_verbose}; #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # Box Family of primitive control sequences @@ -59,7 +59,7 @@ DefConstructor('\lx@hidden@egroup', '', DefConstructor('\lx@framed[]{}', "#2", - properties => { frame => sub { ToString($_[1] || 'rectangle'); } }, + properties => { frame => sub { ToString($_[1] || 'rectangle'); } }, enterHorizontal => 1); DefConstructor('\lx@hflipped{}', @@ -93,7 +93,7 @@ DefPrimitive('\lx@math@nounicode DefToken', sub { DefConstructor('\lx@text@nounicode DefToken', "#1", enterHorizontal => 1, - afterDigest => sub { + afterDigest => sub { reportNoUnicode(ToString($_[1]->getArg(0))); }); #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -136,35 +136,35 @@ sub readBoxContents { my $stomach = $STATE->getStomach; my $t; while (($t = $gullet->readToken) && !$t->defined_as(T_BEGIN)) { } # Skip till { or \bgroup - # Now, insert some extra tokens, if any, possibly from \afterassignment + # Now, insert some extra tokens, if any, possibly from \afterassignment if (my $token = LookupValue('BeforeNextBox')) { AssignValue(BeforeNextBox => undef, 'global'); $gullet->unread($token); } # AND, insert any extra tokens passed in, due to everyhbox or everyvbox $gullet->unread($everybox->unlist) if $everybox; - $stomach->beginMode($mode); # and new group! + $stomach->beginMode($mode); # and new group! my $token; # Like $stomach->invokeToken(T_BEGIN), but building List in correct mode local @LaTeXML::LIST = (); + my $level = $STATE->getFrameDepth; while (defined($t = $gullet->getPendingComment || $gullet->readXToken(1))) { - last if $t->defined_as(T_END); + last if $t->defined_as(T_END) && ($level >= $STATE->getFrameDepth); push(@LaTeXML::LIST, $stomach->invokeToken($t)); } - $stomach->endMode($mode); # end mode, and close extra group - return List(@LaTeXML::LIST, mode=>$mode); } + $stomach->endMode($mode); # end mode, and close extra group + return List(@LaTeXML::LIST, mode => $mode); } DefRegister('\everyhbox', Tokens()); DefRegister('\everyvbox', Tokens()); DefParameterType('HBoxContents', sub { readBoxContents($_[0], LookupValue('\everyhbox'), 'restricted_horizontal'); }, - undigested => 1, # Cause it already is digested! - reversion => sub { (T_BEGIN, Revert($_[0]), T_END); }); + undigested => 1, # Cause it already is digested! + reversion => sub { (T_BEGIN, Revert($_[0]), T_END); }); DefParameterType('VBoxContents', sub { readBoxContents($_[0], LookupValue('\everyvbox'), 'internal_vertical'); }, - undigested => 1, # Cause it already is digested! - reversion => sub { (T_BEGIN, Revert($_[0]), T_END); }); - + undigested => 1, # Cause it already is digested! + reversion => sub { (T_BEGIN, Revert($_[0]), T_END); }); # Obsolete/Deprecated sub reenterTextMode { @@ -274,7 +274,7 @@ sub collapseSVGGroup { # Typically will be within an svg:g which establishes position & scale. # in which case (see below), it will be moved to be the last child so it overlays sub addSVGDebuggingBox { - my($document,$x,$y,$w,$h,$color)=@_; + my ($document, $x, $y, $w, $h, $color) = @_; ($x, $y, $w, $h) = map { (ref $_ ? $_->pxValue : $_); } ($x, $y, $w, $h); my $x1 = $x + $w; my $y1 = $y + $h; @@ -290,12 +290,12 @@ sub addSVGDebuggingBox { # Move any debugging boxes to the end of their svg:g, so they overlay the content Tag('svg:g', afterClose => sub { - my ($document, $node) = @_; - my ($first,@more) = element_nodes($node); - if($first && $first->getAttribute('_svg_debug_box') && @more) { - $node->removeChild($first); - $node->appendChild($first); } - return; }); + my ($document, $node) = @_; + my ($first, @more) = element_nodes($node); + if ($first && $first->getAttribute('_svg_debug_box') && @more) { + $node->removeChild($first); + $node->appendChild($first); } + return; }); DefConstructor('\hbox BoxSpecification HBoxContents', sub { # "#2", @@ -307,21 +307,21 @@ DefConstructor('\hbox BoxSpecification HBoxContents', sub { # What is the CORRECT (& general) way to ask whether we're in "vertical mode"?? # my $vmode = $tag eq 'ltx:inline-block'; # ie, explicitly \vbox !?!?!?! my $issvg = $current && $tag =~ /^svg:/; - my $vmode = $current && ($tag eq 'ltx:_CaptureBlock_'); # if going into insertBlock + my $vmode = $current && ($tag eq 'ltx:_CaptureBlock_'); # if going into insertBlock my $inline = $document->canContain($current, '#PCDATA'); my $newtag = ($issvg ? 'svg:g' : ($vmode ? ($inline ? 'ltx:inline-block' : 'ltx:p') : 'ltx:text')); my $node = $document->openElement($newtag, _noautoclose => 1, width => $props{width}); $document->absorb($contents); - if($issvg && $LaTeXML::DEBUG{svg} && $document->findnodes('svg:foreignObject',$node)){ + if ($issvg && $LaTeXML::DEBUG{svg} && $document->findnodes('svg:foreignObject', $node)) { my ($w, $h, $d) = map { $_->pxValue; } $_[2]->getSize; - addSVGDebuggingBox($document,0,-$d,$w,$h+$d,'#00FFFF'); } - if (!$issvg) { # If not directly svg, but nested within closeable (???) + addSVGDebuggingBox($document, 0, -$d, $w, $h + $d, '#00FFFF'); } + if (!$issvg) { # If not directly svg, but nested within closeable (???) while (!$document->getElement()->hasAttribute('_beginscope') && $document->maybeCloseElement('svg:g')) { } $document->maybeCloseElement('svg:svg'); } $document->maybeCloseNode($node); }, - mode => 'restricted_horizontal', - sizer => '#2', + mode => 'restricted_horizontal', + sizer => '#2', afterDigest => sub { my ($stomach, $whatsit) = @_; my $spec = $whatsit->getArg(1); @@ -371,25 +371,25 @@ Tag('svg:foreignObject', autoOpen => 1, autoClose => 1, my $x = Dimension(0); my $y = $h; my $f = $whatsit->getFont; - Debug("SVG:foreignObject ".$w->pxValue.' x '.$h->pxValue.' x '.$d->pxValue."; " - . " => H=".$H->pxValue." @ y=".$y->pxValue - . " for Whatsit=".ToString($whatsit)) if $LaTeXML::DEBUG{svg_verbose}; + Debug("SVG:foreignObject " . $w->pxValue . ' x ' . $h->pxValue . ' x ' . $d->pxValue . "; " + . " => H=" . $H->pxValue . " @ y=" . $y->pxValue + . " for Whatsit=" . ToString($whatsit)) if $LaTeXML::DEBUG{svg_verbose}; # Size of FO is width and total height (+ depth) - $node->setAttribute(width => $W->pxValue) unless $node->hasAttribute('width'); - $node->setAttribute(height => $H->pxValue) unless $node->hasAttribute('height'); + $node->setAttribute(width => $W->pxValue) unless $node->hasAttribute('width'); + $node->setAttribute(height => $H->pxValue) unless $node->hasAttribute('height'); # Set top of FO height above current, as if baseline will line up w/outside - $node->setAttribute(transform => 'matrix(1 0 0 -1 '.$x->pxValue.' '.$y->pxValue.')'); + $node->setAttribute(transform => 'matrix(1 0 0 -1 ' . $x->pxValue . ' ' . $y->pxValue . ')'); $node->setAttribute(overflow => 'visible'); # Store expected size for experimentation Temporary? $document->setAttribute($node, - style=>'--ltx-fo-width:' . $w->emValue(undef,$f).'em;' - .'--ltx-fo-height:' . $h->emValue(undef,$f).'em;' - .'--ltx-fo-depth:' . $d->emValue(undef,$f).'em;'); - } }); + style => '--ltx-fo-width:' . $w->emValue(undef, $f) . 'em;' + . '--ltx-fo-height:' . $h->emValue(undef, $f) . 'em;' + . '--ltx-fo-depth:' . $d->emValue(undef, $f) . 'em;'); + } }); sub isVAttached { my ($node) = @_; - while($node) { + while ($node) { return 1 if $node->hasAttribute('vattach'); my (@children) = $node->childNodes; return if (scalar(@children) != 1) || ($children[0]->nodeType != XML_ELEMENT_NODE); @@ -417,7 +417,7 @@ sub insertBlock { if (($context_tag =~ /^ltx:XM/) && ($context_tag ne 'ltx:XMText')) { # but math always needs this $context = $document->openElement('ltx:XMText'); $context_tag = $document->getNodeQName($context); } - if(my $w = $is_svg && $blockattr{width}) { + if (my $w = $is_svg && $blockattr{width}) { $blockattr{width} = $w->emValue(undef, $contents->getFont) . "em" if ref $w; } my $inline = $is_svg || $document->canContain($context_tag, '#PCDATA'); my $container = $document->openElement('ltx:_CaptureBlock_', %blockattr); @@ -429,9 +429,9 @@ sub insertBlock { $document->closeNode($container); $document->closeToNode($context, 1); # Hack: apparently TeX doesn't shift (vattach) a single node in a vbox/vtop/... - if(($nnodes == 1) && exists($blockattr{vattach}) && isVAttached($nodes[0])) { + if (($nnodes == 1) && exists($blockattr{vattach}) && isVAttached($nodes[0])) { $container->removeAttribute('vattach'); - delete $blockattr{vattach}; + delete $blockattr{vattach}; $ignorable_attr = $is_svg || !scalar(keys %blockattr); } my $newcontainer; if ($nnodes < 1) { # Insertion came up empty? @@ -477,10 +477,10 @@ sub hackVBoxAttachment { # APPARENTLY \halign in TeX is processed into a internal_vertical list # which can get the valign adjustment. So we need to copy it over to the Alignment # But this does NOT affect {tabular} environments! - if(my $alignment = $box && $box->getProperty('alignment')){ + if (my $alignment = $box && $box->getProperty('alignment')) { my $def = $box->getDefinition; - if($def && ($def->getCSName eq '\halign')){ - if(my $attr = $alignment->getProperty('attributes')){ + if ($def && ($def->getCSName eq '\halign')) { + if (my $attr = $alignment->getProperty('attributes')) { $$attr{vattach} = $valign; } } } else { $box->setProperty(vattach => $valign); } @@ -490,8 +490,8 @@ DefConstructor('\vbox BoxSpecification VBoxContents', sub { my ($document, $spec, $contents, %props) = @_; # is_vbox property detects nested \vbox|\vtop : only inner one affects vattach! my @block = ($contents->getProperty('is_vbox') - ? $document->absorb($contents) - : insertBlock($document, $contents, vattach => 'bottom')); }, + ? $document->absorb($contents) + : insertBlock($document, $contents, vattach => 'bottom')); }, sizer => '#2', properties => { vattach => 'bottom' }, mode => 'internal_vertical', @@ -505,15 +505,15 @@ DefConstructor('\vbox BoxSpecification VBoxContents', sub { elsif (my $s = GetKeyVal($spec, 'spread')) { $whatsit->setHeight($box->getHeight->add($s)); } $whatsit->setProperty(content_box => $box); - $whatsit->setProperty(is_vbox => 1); + $whatsit->setProperty(is_vbox => 1); return; }); DefConstructor('\vtop BoxSpecification VBoxContents', sub { my ($document, $spec, $contents, %props) = @_; # is_vbox property detects nested \vbox|\vtop : only inner one affects vattach! my @block = ($contents->getProperty('is_vbox') - ? $document->absorb($contents) - : insertBlock($document, $contents, vattach => 'top')); }, + ? $document->absorb($contents) + : insertBlock($document, $contents, vattach => 'top')); }, sizer => '#2', properties => { vattach => 'top' }, mode => 'internal_vertical', @@ -527,7 +527,7 @@ DefConstructor('\vtop BoxSpecification VBoxContents', sub { elsif (my $s = GetKeyVal($spec, 'spread')) { $whatsit->setHeight($box->getHeight->add($s)); } $whatsit->setProperty(content_box => $box); - $whatsit->setProperty(is_vbox => 1); + $whatsit->setProperty(is_vbox => 1); return; }); #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -561,7 +561,7 @@ DefPrimitive('\setbox Number SkipSpaces SkipMatch:=', sub { # Should afterDigest be responsible for resetting flags? my $scope = $STATE->getPrefix('global') && 'global'; $STATE->clearPrefixes; # before invoke, below; we've saved the only relevant one (global) - # Digest box starting from Default color, background; TeX Boxes don't record these. + # Digest box starting from Default color, background; TeX Boxes don't record these. my $font = $STATE->lookupValue('font'); $STATE->assignValue(font => $font->merge(color => Black, background => undef)); my ($stuff, @rest) = $stomach->invokeToken($stomach->getGullet->readXToken); @@ -605,11 +605,11 @@ DefRegister('\dp Number', Dimension(0), # Adjust the default color of $box to be the current color, # creating a copy of the box if needed. sub adjustBoxColor { - my ($box) = @_; - my $font = LookupValue('font'); - my $color = $font && $font->getColor; + my ($box) = @_; + my $font = LookupValue('font'); + my $color = $font && $font->getColor; my $bgcolor = $font && $font->getBackground; - if(($color && !Black->equals($color)) || ($bgcolor && !White->equals($bgcolor))){ + if (($color && !Black->equals($color)) || ($bgcolor && !White->equals($bgcolor))) { return _color_adjust($color, $bgcolor, {}, $box); } return $box; } @@ -622,77 +622,77 @@ sub _color_adjust { return $object unless $object; return $$cache{$object} if $$cache{$object}; # Nested, possibly circular, structures my $type = ref $object; - my $adj = $object; - if($type eq 'ARRAY'){ + my $adj = $object; + if ($type eq 'ARRAY') { $adj = [map { _color_adjust($fg, $bg, $cache, $_); } @$object]; } - elsif($type eq 'HASH'){ - $adj = {map { $_=>_color_adjust($fg, $bg, $cache, $$object{$_}); } keys %$object}; } + elsif ($type eq 'HASH') { + $adj = { map { $_ => _color_adjust($fg, $bg, $cache, $$object{$_}); } keys %$object }; } # NASTY access to internal structure; but worth a whole API for this one hack??? - elsif ($type =~ /^LaTeXML::Core::(?:Box|List|Whatsit|Alignment)$/) { # blessed hashes! - $$cache{$object} = $adj = bless {%$object}, $type; # Cache BEFORE recursion! + elsif ($type =~ /^LaTeXML::Core::(?:Box|List|Whatsit|Alignment)$/) { # blessed hashes! + $$cache{$object} = $adj = bless {%$object}, $type; # Cache BEFORE recursion! map { $$adj{$_} = _color_adjust($fg, $bg, $cache, $$object{$_}); } keys %$object; } - elsif($type eq 'LaTeXML::Common::Font'){ + elsif ($type eq 'LaTeXML::Common::Font') { # Replace "default" color & background color by the requested $fg,$bg. # We'll use eq (of refs) hoping that detects inherited defaults, # rather than explicitly assigned black or white colors. $adj = $object; - $adj = $adj->merge(color => $fg) if Black eq ($object->getColor || Black); + $adj = $adj->merge(color => $fg) if Black eq ($object->getColor || Black); $adj = $adj->merge(background => $bg) if White eq ($object->getBackground || White); } $$cache{$object} = $adj; return $adj; } DefPrimitive('\box Number', sub { - my ($stomach, $n) = @_; - my $box = 'box' . $n->valueOf; - my $stuff = adjustBoxColor(LookupValue($box)); - AssignValue($box, undef, 'inplace'); - return ($stuff ? $stuff : ()); }); + my ($stomach, $n) = @_; + my $box = 'box' . $n->valueOf; + my $stuff = adjustBoxColor(LookupValue($box)); + AssignValue($box, undef, 'inplace'); + return ($stuff ? $stuff : ()); }); DefPrimitive('\copy Number', sub { - my ($stomach, $n) = @_; - my $box = 'box' . $n->valueOf; - my $stuff = adjustBoxColor(LookupValue($box)); - return ($stuff ? $stuff : ()); }); + my ($stomach, $n) = @_; + my $box = 'box' . $n->valueOf; + my $stuff = adjustBoxColor(LookupValue($box)); + return ($stuff ? $stuff : ()); }); # \unhbox<8bit>, \unhcopy<8bit> DefPrimitive('\unhbox Number', sub { - my ($stomach, $n) = @_; - my $box = 'box' . $n->valueOf; - my $stuff = adjustBoxColor(LookupValue($box)); - my $m = ($stuff && $stuff->getProperty('mode')) || ''; - $stomach->enterHorizontal; - AssignValue($box, undef, 'inplace'); - # Only unlist if box is horizontal - return (!defined $stuff ? () : ($m =~ /horizontal$/ ? $stuff->unlist : $stuff)); }); + my ($stomach, $n) = @_; + my $box = 'box' . $n->valueOf; + my $stuff = adjustBoxColor(LookupValue($box)); + my $m = ($stuff && $stuff->getProperty('mode')) || ''; + $stomach->enterHorizontal; + AssignValue($box, undef, 'inplace'); + # Only unlist if box is horizontal + return (!defined $stuff ? () : ($m =~ /horizontal$/ ? $stuff->unlist : $stuff)); }); DefPrimitive('\unhcopy Number', sub { - my ($stomach, $n) = @_; - my $box = 'box' . $n->valueOf; - my $stuff = adjustBoxColor(LookupValue($box)); - my $m = ($stuff && $stuff->getProperty('mode')) || ''; - $stomach->enterHorizontal; - # Only unlist if box is horizontal - return (!defined $stuff ? () : ($m =~ /horizontal$/ ? $stuff->unlist : $stuff)); }); + my ($stomach, $n) = @_; + my $box = 'box' . $n->valueOf; + my $stuff = adjustBoxColor(LookupValue($box)); + my $m = ($stuff && $stuff->getProperty('mode')) || ''; + $stomach->enterHorizontal; + # Only unlist if box is horizontal + return (!defined $stuff ? () : ($m =~ /horizontal$/ ? $stuff->unlist : $stuff)); }); # \unvbox<8bit>, \unvcopy<8bit> DefPrimitive('\unvbox Number', sub { - my ($stomach, $n) = @_; - my $box = 'box' . $n->valueOf; - my $stuff = adjustBoxColor(LookupValue($box)); - my $m = ($stuff && $stuff->getProperty('mode')) || ''; - $stomach->leaveHorizontal; - AssignValue($box, undef, 'inplace'); - # Only unlist if box is vertical - return (!defined $stuff ? () : ($m =~ /vertical$/ ? $stuff->unlist : $stuff)); }); + my ($stomach, $n) = @_; + my $box = 'box' . $n->valueOf; + my $stuff = adjustBoxColor(LookupValue($box)); + my $m = ($stuff && $stuff->getProperty('mode')) || ''; + $stomach->leaveHorizontal; + AssignValue($box, undef, 'inplace'); + # Only unlist if box is vertical + return (!defined $stuff ? () : ($m =~ /vertical$/ ? $stuff->unlist : $stuff)); }); DefPrimitive('\unvcopy Number', sub { - my ($stomach, $n) = @_; - my $box = 'box' . $n->valueOf; - my $stuff = adjustBoxColor(LookupValue($box)); - my $m = ($stuff && $stuff->getProperty('mode')) || ''; - $stomach->leaveHorizontal; - # Only unlist if box is vertical - return (!defined $stuff ? () : ($m =~ /vertical$/ ? $stuff->unlist : $stuff)); }); + my ($stomach, $n) = @_; + my $box = 'box' . $n->valueOf; + my $stuff = adjustBoxColor(LookupValue($box)); + my $m = ($stuff && $stuff->getProperty('mode')) || ''; + $stomach->leaveHorizontal; + # Only unlist if box is vertical + return (!defined $stuff ? () : ($m =~ /vertical$/ ? $stuff->unlist : $stuff)); }); #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # Various box related parameters diff --git a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml index 5751e29d5..97d5b676c 100644 --- a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml +++ b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml @@ -3430,6 +3430,7 @@ DefConstructor('\@@toccaption{}', "^^#1", sub beforeFloat { my ($type, %options) = @_; DefMacroI('\@captype', undef, $type); + Let('\\\\', '\lx@newline'); AssignRegister('\hsize' => LookupDimension($options{double} ? '\textwidth' : '\columnwidth')); if (my $main = $options{preincrement}) { if (($type ne (LookupValue('LAST_FLOATTYPE') || '')) # first of subtype?