From d7c0e9bafeb81c7a71120560a43770082fe99ddb Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 23 Apr 2026 16:36:53 -0400 Subject: [PATCH 01/31] Have stomach->beginMode do leaveHorizontal, if begining a vertical or display-math mode; introduce a new pseudo-mode inline_internal_vertical which does NOT leaveHorizontal, for (essentially) inline blocks --- lib/LaTeXML/Core/Stomach.pm | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/LaTeXML/Core/Stomach.pm b/lib/LaTeXML/Core/Stomach.pm index b199fc34d..dfed00105 100644 --- a/lib/LaTeXML/Core/Stomach.pm +++ b/lib/LaTeXML/Core/Stomach.pm @@ -28,7 +28,7 @@ use LaTeXML::Common::Font; use LaTeXML::Common::Color; use LaTeXML::Core::Definition; use Scalar::Util qw(blessed); -use base qw(LaTeXML::Common::Object); +use base qw(LaTeXML::Common::Object); DebuggableFeature('modes'); @@ -398,13 +398,14 @@ 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', + inline_internal_vertical => 'internal_vertical', # BUT w/o leaveHorizontal + 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, @@ -476,9 +477,13 @@ sub leaveHorizontal_internal { sub beginMode { my ($self, $umode, $noframe) = @_; if (my $mode = $bindable_mode{$umode}) { + my $ismath = $mode =~ /math$/; + my $isdisplay = $mode =~ /^display/; + my $isvertical = $isdisplay || ($mode =~ /vertical/); + my $isinline = $umode =~ /inline/; + $self->leaveHorizontal if $isvertical && !$isinline; my $prevmode = $STATE->lookupValue('MODE'); my $prevbound = $STATE->lookupValue('BOUND_MODE'); - 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! @@ -495,8 +500,7 @@ sub beginMode { # and save the text font for any embedded text. $STATE->assignValue(savedfont => $curfont, 'local'); $STATE->assignValue(script_base_level => scalar(@{ $$self{boxing} })); # See getScriptLevel - my $isdisplay = $mode =~ /^display/; - my $mathfont = $STATE->lookupValue('mathfont')->merge( + my $mathfont = $STATE->lookupValue('mathfont')->merge( color => $curfont->getColor, background => $curfont->getBackground, size => $curfont->getSize, mathstyle => ($isdisplay ? 'display' : 'text')); @@ -517,7 +521,6 @@ sub beginMode { else { Warn('unexpected', $mode, $self, "Cannot enter $mode mode"); } return; } - # End the mode $umode; generally pops the stack frome. # In RARE cases, we mignt want the same effect, w/o having pushed a stack frome (see above) # In that case, we'll still want to do BeforeAfterGroup as-if we had an end group. @@ -537,7 +540,7 @@ sub endMode { 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"); } return; } From 76ace44384d9dab507d08f72a2f841845755ee51 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 23 Apr 2026 16:49:01 -0400 Subject: [PATCH 02/31] Add mode=internal_vertical to constructors which need it; morphing to mode=inline_internal_vertical for those that are vertical inside, but do not leaveHorizontal; Remove the mistaken \par from the various itemization environments --- lib/LaTeXML/Engine/TeX_Box.pool.ltxml | 6 +-- .../Engine/latex_constructs.pool.ltxml | 37 ++++++++----------- lib/LaTeXML/Package/IEEEtran.cls.ltxml | 17 ++++----- lib/LaTeXML/Package/alltt.sty.ltxml | 5 ++- lib/LaTeXML/Package/beamer.cls.ltxml | 22 +++++------ lib/LaTeXML/Package/elsarticle.cls.ltxml | 10 ++--- lib/LaTeXML/Package/enumerate.sty.ltxml | 2 +- lib/LaTeXML/Package/enumitem.sty.ltxml | 12 ++---- 8 files changed, 49 insertions(+), 62 deletions(-) diff --git a/lib/LaTeXML/Engine/TeX_Box.pool.ltxml b/lib/LaTeXML/Engine/TeX_Box.pool.ltxml index 2de77264e..987bef3dd 100644 --- a/lib/LaTeXML/Engine/TeX_Box.pool.ltxml +++ b/lib/LaTeXML/Engine/TeX_Box.pool.ltxml @@ -385,7 +385,7 @@ Tag('svg:foreignObject', autoOpen => 1, autoClose => 1, 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) = @_; @@ -494,7 +494,7 @@ DefConstructor('\vbox BoxSpecification VBoxContents', sub { : insertBlock($document, $contents, vattach => 'bottom')); }, sizer => '#2', properties => { vattach => 'bottom' }, - mode => 'internal_vertical', + mode => 'inline_internal_vertical', afterDigest => sub { my ($stomach, $whatsit) = @_; my $spec = $whatsit->getArg(1); @@ -516,7 +516,7 @@ DefConstructor('\vtop BoxSpecification VBoxContents', sub { : insertBlock($document, $contents, vattach => 'top')); }, sizer => '#2', properties => { vattach => 'top' }, - mode => 'internal_vertical', + mode => 'inline_internal_vertical', afterDigest => sub { my ($stomach, $whatsit) = @_; my $spec = $whatsit->getArg(1); diff --git a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml index 50e3eb610..0632898ae 100644 --- a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml +++ b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml @@ -451,7 +451,7 @@ DefConstructor('\lx@note[]{}[]{}', . "#tags" . "#4" . "", - mode => 'internal_vertical', + mode => 'inline_internal_vertical', sizer => '#mark', beforeDigest => sub { neutralizeFont(); }, properties => sub { @@ -473,7 +473,7 @@ DefConstructor('\lx@notemark[]{}[]', reversion => ''); DefConstructor('\lx@notetext[]{}[]{}', "^#4", - mode => 'internal_vertical', + mode => 'inline_internal_vertical', properties => sub { my $type = ToString($_[2]); (role => $type . 'text', makeNoteTags($type, $_[1], $_[3] || DigestText(T_CS('\the' . $type)))); }, @@ -1477,23 +1477,20 @@ DefConstructor('\inline@description@item OptionalUndigested', DefEnvironment('{itemize}', "#body", - mode => 'internal_vertical', - properties => sub { beginItemize('itemize', '@item'); }, - beforeDigestEnd => sub { Digest('\par'); }, - locked => 1); + mode => 'internal_vertical', + properties => sub { beginItemize('itemize', '@item'); }, + locked => 1); DefEnvironment('{enumerate}', "#body", - mode => 'internal_vertical', - properties => sub { beginItemize('enumerate', 'enum'); }, - beforeDigestEnd => sub { Digest('\par'); }, - locked => 1); + mode => 'internal_vertical', + properties => sub { beginItemize('enumerate', 'enum'); }, + locked => 1); DefEnvironment('{description}', "#body", - mode => 'internal_vertical', - beforeDigest => sub { Let('\makelabel', '\descriptionlabel'); }, - properties => sub { beginItemize('description', '@desc'); }, - beforeDigestEnd => sub { Digest('\par'); }, - locked => 1); + mode => 'internal_vertical', + beforeDigest => sub { Let('\makelabel', '\descriptionlabel'); }, + properties => sub { beginItemize('description', '@desc'); }, + locked => 1); DefMacro('\makelabel{}', '#1'); DefMacro('\@mklab{}', '\hfil #1'); @@ -1661,8 +1658,7 @@ DefConstructor('\trivlist', mode => 'internal_vertical', properties => sub { beginItemize('trivlist'); }); DefConstructor('\endtrivlist', sub { - $_[0]->maybeCloseElement('ltx:itemize'); }, - beforeDigest => sub { Digest('\par'); }); + $_[0]->maybeCloseElement('ltx:itemize'); }); DefMacro('\trivlist@item', '\preitem@par\trivlist@item@'); DefConstructor('\trivlist@item@ OptionalUndigested', "" @@ -1750,7 +1746,6 @@ sub beforeDigestVerbatim { AssignValue(current_environment => 'verbatim'); DefMacroI('\@currenvir', undef, 'verbatim'); MergeFont(family => 'typewriter'); - # Digest(T_CS('\par')); # NO! See beforeConstruct! return @stuff; } sub afterDigestVerbatim { @@ -4751,7 +4746,7 @@ DefConstructor('\lx@parbox[][Dimension] OptionalUndigested {Dimension} VBoxConte (width => $_[4], vattach => translateAttachment($_[1]), height => $_[2]); }, - mode => 'internal_vertical', + mode => 'inline_internal_vertical', robust => 1, beforeDigest => sub { Let('\\\\', '\lx@newline'); }); @@ -4768,7 +4763,7 @@ DefEnvironment('{minipage}[] OptionalUndigested [] {Dimension}', sub { class => 'ltx_minipage'); return; }, sizer => '#body', - mode => 'internal_vertical', + mode => 'inline_internal_vertical', beforeDigest => sub { Digest(T_CS('\@minipagetrue')); }, afterDigestBegin => sub { @@ -4951,7 +4946,7 @@ DefEnvironment('{picture} Pair OptionalPair', . " fill='none' stroke='none' unitlength='#unitlength'>" . "?#transform(#body)(#body)" . "", - mode => 'internal_vertical', + mode => 'inline_internal_vertical', beforeDigest => \&before_picture, properties => sub { my ($stomach, $size, $pos) = @_; diff --git a/lib/LaTeXML/Package/IEEEtran.cls.ltxml b/lib/LaTeXML/Package/IEEEtran.cls.ltxml index e2c363b32..052d25dfe 100644 --- a/lib/LaTeXML/Package/IEEEtran.cls.ltxml +++ b/lib/LaTeXML/Package/IEEEtran.cls.ltxml @@ -343,20 +343,17 @@ DefMacro('\IEEEiedlabeljustifyr', ''); # also, we skip the internal @-named variants for now. DefEnvironment('{IEEEitemize}[]', "#body", - properties => sub { beginItemize('itemize', '@item'); }, - beforeDigestEnd => sub { Digest('\par'); }, - locked => 1, mode => 'internal_vertical'); + properties => sub { beginItemize('itemize', '@item'); }, + locked => 1, mode => 'internal_vertical'); DefEnvironment('{IEEEenumerate}[]', "#body", - properties => sub { beginItemize('enumerate', 'enum'); }, - beforeDigestEnd => sub { Digest('\par'); }, - locked => 1, mode => 'internal_vertical'); + properties => sub { beginItemize('enumerate', 'enum'); }, + locked => 1, mode => 'internal_vertical'); DefEnvironment('{IEEEdescription}[]', "#body", - beforeDigest => sub { Let('\makelabel', '\descriptionlabel'); }, - properties => sub { beginItemize('description', '@desc'); }, - beforeDigestEnd => sub { Digest('\par'); }, - locked => 1, mode => 'internal_vertical'); + beforeDigest => sub { Let('\makelabel', '\descriptionlabel'); }, + properties => sub { beginItemize('description', '@desc'); }, + locked => 1, mode => 'internal_vertical'); # override LaTeX's default IED lists Let('\itemize', '\IEEEitemize'); diff --git a/lib/LaTeXML/Package/alltt.sty.ltxml b/lib/LaTeXML/Package/alltt.sty.ltxml index ec05722ab..f38ec5077 100644 --- a/lib/LaTeXML/Package/alltt.sty.ltxml +++ b/lib/LaTeXML/Package/alltt.sty.ltxml @@ -18,12 +18,13 @@ use LaTeXML::Package; #********************************************************************** DefEnvironment('{alltt}', "#body", - font => { family => 'typewriter', series => 'medium', shape => 'upright' }, + font => { family => 'typewriter', series => 'medium', shape => 'upright' }, + mode => 'internal_vertical', beforeDigest => sub { map { AssignCatcode($_ => CC_OTHER) } '$', '&', '#', '^', '_', '%', '~'; AssignCatcode(" " => 13); Let(T_ACTIVE(" "), '\space'); - AssignCatcode("\r" => 13); # Variant of \obeylines + AssignCatcode("\r" => 13); # Variant of \obeylines Let(T_ACTIVE("\r"), Token("\n", CC_SPACE)); # More appropriate than \par, I think? AssignValue(PRESERVE_NEWLINES => 1); # \@noligs: This SHOULD inhibit ligature substitution! (eg quotes, dots, etc!!!) diff --git a/lib/LaTeXML/Package/beamer.cls.ltxml b/lib/LaTeXML/Package/beamer.cls.ltxml index 3e77aebad..c1ccfe930 100644 --- a/lib/LaTeXML/Package/beamer.cls.ltxml +++ b/lib/LaTeXML/Package/beamer.cls.ltxml @@ -98,7 +98,7 @@ sub readLiteralBalanced { my ($gullet) = @_; my $tok = $gullet->readXToken; $gullet->unread($tok); - return undef unless (ref $tok && ToString($tok) eq '{'); + return unless (ref $tok && ToString($tok) eq '{'); return $gullet->readArg; } #********************************************************************** @@ -236,7 +236,7 @@ sub matchesModeSpec { # process the specification into a list of modes! $spec =~ s/\s//g; my $haystacks = $BEAMER_SPECIFICATION->mode_spec($spec); - return undef unless defined($haystacks); + return unless defined($haystacks); # iterate over the list of modes, and check if at least one of them matches foreach my $haystack (@{$haystacks}) { return 1 if matchesMode($haystack, $mode); } @@ -338,7 +338,7 @@ sub getNextSlide { while ($temporal == -1) { $no++; $temporal = getTemporalSlide($actions, $no); } - return undef if ($temporal == 1); # we don't have another slide! + return if ($temporal == 1); # we don't have another slide! $no; } #********************************************************************** @@ -375,7 +375,7 @@ sub digestBeamerSpec { # See getTemporalSlide for return values. sub digestOverlaySpec { my ($spec) = @_; - return undef unless defined($spec); + return unless defined($spec); $spec = ToString(Expand($spec)); # TODO: Should this be done ealier? Perhaps in the argument type? # get the action my $action = digestBeamerSpec($spec, 0); @@ -394,14 +394,14 @@ sub digestOverlaySpec { # See getSlideActions for return values. sub digestActionSpec { my ($spec) = @_; - return undef unless defined($spec); + return unless defined($spec); $spec = ToString(Expand($spec)); # TODO: Should this be done ealier? Perhaps in the argument type? # get the action my $action = digestBeamerSpec($spec, 1); unless (defined($action)) { Warn('unexpected', '', $spec, 'Missing overlay specification, treated as matching'); return 0; } - return undef unless defined($action); + return unless defined($action); # check if we need to do anything getSlideActions($action); } @@ -1160,22 +1160,22 @@ DefMacro('\beamer@item@before BeamerAngled []', sub { DefEnvironment('{itemize} [BeamerAngled]', "#body", properties => sub { beginBeamerItemize($_[1], 'itemize', '@item'); }, - beforeDigestEnd => sub { Digest('\par'); Digest('\beamer@closeitem'); }, + beforeDigestEnd => sub { Digest('\beamer@closeitem'); }, locked => 1, mode => 'internal_vertical'); # from enumitem package, because of the second arg! DefEnvironment('{enumerate} [BeamerAngled] OptionalUndigested', "#body", properties => sub { beginBeamerItemize($_[1], 'enumerate', 'enum'); }, - beforeDigestEnd => sub { Digest('\par'); Digest('\beamer@closeitem'); }, - afterDigestBegin => sub { setEnumerationStyle($_[1]->getArg(2)); }); - + beforeDigestEnd => sub { Digest('\beamer@closeitem'); }, + afterDigestBegin => sub { setEnumerationStyle($_[1]->getArg(2)); }, + locked => 1, mode => 'internal_vertical'); # from LaTeX.Pool DefEnvironment('{description} [BeamerAngled]', "#body", beforeDigest => sub { Let('\makelabel', '\descriptionlabel'); }, properties => sub { beginBeamerItemize($_[1], 'description', '@desc'); }, - beforeDigestEnd => sub { Digest('\par'); Digest('\beamer@closeitem'); }, + beforeDigestEnd => sub { Digest('\beamer@closeitem'); }, locked => 1, mode => 'internal_vertical'); #********************************************************************** diff --git a/lib/LaTeXML/Package/elsarticle.cls.ltxml b/lib/LaTeXML/Package/elsarticle.cls.ltxml index 1b3d869fe..fee20f983 100644 --- a/lib/LaTeXML/Package/elsarticle.cls.ltxml +++ b/lib/LaTeXML/Package/elsarticle.cls.ltxml @@ -57,13 +57,11 @@ DefMacro('\biboptions{}', '\setcitestyle{#1}'); # (not even sure what the intended effect is) DefEnvironment('{enumerate}[]', "#body", - properties => sub { beginItemize('enumerate', 'enum'); }, - beforeDigestEnd => sub { Digest('\par'); }, - locked => 1, mode => 'internal_vertical'); + properties => sub { beginItemize('enumerate', 'enum'); }, + locked => 1, mode => 'internal_vertical'); DefEnvironment('{itemize}[]', "#body", - properties => sub { beginItemize('itemize', 'enum'); }, - beforeDigestEnd => sub { Digest('\par'); }, - locked => 1, mode => 'internal_vertical'); + properties => sub { beginItemize('itemize', 'enum'); }, + locked => 1, mode => 'internal_vertical'); 1; diff --git a/lib/LaTeXML/Package/enumerate.sty.ltxml b/lib/LaTeXML/Package/enumerate.sty.ltxml index 6109b0704..e4b90b164 100644 --- a/lib/LaTeXML/Package/enumerate.sty.ltxml +++ b/lib/LaTeXML/Package/enumerate.sty.ltxml @@ -18,8 +18,8 @@ use LaTeXML::Package; # Redefines LaTeX's enumerate to take an additional style argument DefEnvironment('{enumerate} OptionalUndigested', "#body", + mode => 'internal_vertical', properties => sub { beginItemize('enumerate', 'enum'); }, - beforeDigestEnd => sub { Digest('\par'); }, afterDigestBegin => sub { setEnumerationStyle($_[1]->getArg(1)); }); 1; diff --git a/lib/LaTeXML/Package/enumitem.sty.ltxml b/lib/LaTeXML/Package/enumitem.sty.ltxml index c2c16eb52..e65c7794e 100644 --- a/lib/LaTeXML/Package/enumitem.sty.ltxml +++ b/lib/LaTeXML/Package/enumitem.sty.ltxml @@ -144,14 +144,12 @@ if (!LookupValue('enumitem@loadonly')) { DefEnvironment('{itemize} OptionalKeyVals:enumitem', "#body", properties => sub { beginEnumItemize('itemize', '@item', $_[1]); }, - beforeDigestEnd => sub { Digest('\par'); }, afterDigestBody => sub { endEnumItemize($_[1]); }, mode => 'internal_vertical', locked => 1); DefEnvironment('{enumerate} OptionalKeyVals:enumitem', "#body", properties => sub { beginEnumItemize('enumerate', 'enum', $_[1]); }, - beforeDigestEnd => sub { Digest('\par'); }, afterDigestBody => sub { endEnumItemize($_[1]); }, mode => 'internal_vertical', locked => 1); @@ -159,7 +157,6 @@ if (!LookupValue('enumitem@loadonly')) { "#body", beforeDigest => sub { Let('\makelabel', '\descriptionlabel'); }, properties => sub { beginEnumItemize('description', '@desc', $_[1]); }, - beforeDigestEnd => sub { Digest('\par'); }, afterDigestBody => sub { endEnumItemize($_[1]); }, mode => 'internal_vertical', locked => 1); @@ -169,17 +166,17 @@ if (LookupValue('enumitem@inline')) { "#body", properties => sub { beginEnumItemize('inline@itemize', '@item', $_[1]); }, afterDigestBody => sub { endEnumItemize($_[1]); }, - mode => 'internal_vertical'); + mode => 'inline_internal_vertical'); DefEnvironment('{enumerate*} OptionalKeyVals:enumitem', "#body", properties => sub { beginEnumItemize('inline@enumerate', 'enum', $_[1]); }, afterDigestBody => sub { endEnumItemize($_[1]); }, - mode => 'internal_vertical'); + mode => 'inline_internal_vertical'); DefEnvironment('{description*} OptionalKeyVals:enumitem', "#body", properties => sub { beginEnumItemize('inline@description', '@desc', $_[1]); }, afterDigestBody => sub { endEnumItemize($_[1]); }, - mode => 'internal_vertical'); + mode => 'inline_internal_vertical'); } DefPrimitive('\newlist{}{}{}', sub { my ($stomach, $listname, $listtype, $maxdepth) = @_; @@ -199,9 +196,8 @@ DefPrimitive('\newlist{}{}{}', sub { DefEnvironmentI($listname, "OptionalKeyVals:enumitem", "#body", properties => sub { beginEnumItemize($listname, $listname, $_[1]); }, - beforeDigestEnd => sub { Digest('\par'); }, afterDigestBody => sub { endEnumItemize($_[1]); }, - mode => 'internal_vertical', + mode => ($inline ? 'inline_internal_vertical' : 'internal_vertical'), locked => 1); return; }); Let('\renewlist', '\newlist'); From e8f8f0efeb44ab1482a1032cde3f40edfab993df Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 23 Apr 2026 16:50:37 -0400 Subject: [PATCH 03/31] Have beforeDigest code directly push to LaTeXML::LIST, rather than return for later inclusion so that the items can be affected by later items or primitives --- lib/LaTeXML/Core/Definition/Constructor.pm | 4 ++-- lib/LaTeXML/Core/Definition/Primitive.pm | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/LaTeXML/Core/Definition/Constructor.pm b/lib/LaTeXML/Core/Definition/Constructor.pm index 1a09d2681..a0cbf593d 100644 --- a/lib/LaTeXML/Core/Definition/Constructor.pm +++ b/lib/LaTeXML/Core/Definition/Constructor.pm @@ -79,7 +79,7 @@ sub invoke { LaTeXML::Core::Definition::startProfiling($profiled, 'digest') if $profiled; - my @pre = $self->executeBeforeDigest($stomach); + $self->executeBeforeDigest($stomach); Debug('{' . $self->tracingCSName . '}') if $tracing; # Get some info before we process arguments... @@ -116,7 +116,7 @@ sub invoke { $whatsit->setBody(@post, $stomach->digestNextBody((ref $cap ? $cap : undef))); @post = (); } my @postpost = $self->executeAfterDigestBody($stomach, $whatsit); LaTeXML::Core::Definition::stopProfiling($profiled, 'digest') if $profiled; - return (@pre, $whatsit, @post, @postpost); } + return ($whatsit, @post, @postpost); } # Similar to executeAfterDigest sub executeAfterDigestBody { diff --git a/lib/LaTeXML/Core/Definition/Primitive.pm b/lib/LaTeXML/Core/Definition/Primitive.pm index f8843e70d..ecb56a0c9 100644 --- a/lib/LaTeXML/Core/Definition/Primitive.pm +++ b/lib/LaTeXML/Core/Definition/Primitive.pm @@ -36,11 +36,16 @@ sub isPrefix { my ($self) = @_; return $$self{isPrefix}; } +# Execute the beforeDigest functions (on $stomach), +# pushing the results (if any) directly on the the @LIST being accumulated. +# returns nothing sub executeBeforeDigest { my ($self, $stomach) = @_; local $LaTeXML::Core::State::UNLOCKED = 1; my @pre = grep { defined } @{ $$self{beforeDigest} || [] }; - return (map { &$_($stomach) } @pre); } + foreach my $f (@pre) { + push(@LaTeXML::LIST, &$f($stomach)); } + return; } sub executeAfterDigest { my ($self, $stomach, @whatever) = @_; @@ -57,7 +62,8 @@ sub invoke { LaTeXML::Core::Definition::startProfiling($profiled, 'digest') if $profiled; Debug('{' . $self->tracingCSName . '}') if $tracing; - my @result = ($self->executeBeforeDigest($stomach)); + $self->executeBeforeDigest($stomach); + my @result = (); my $parms = $$self{parameters}; my @args = ($parms ? $parms->readArguments($stomach->getGullet, $self) : ()); Debug($self->tracingArgs(@args)) if $tracing && @args; From be3c195b2f8d075084a8366cdedac07fe5cef17c Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 23 Apr 2026 16:52:22 -0400 Subject: [PATCH 04/31] Update testcases which now better reflect correct (inline_)internal_vertical modes --- t/complex/equationnest.xml | 6 ++---- t/digestion/dollar.xml | 2 +- t/tokenize/alltt.xml | 44 +++++++++++++++++--------------------- 3 files changed, 23 insertions(+), 29 deletions(-) diff --git a/t/complex/equationnest.xml b/t/complex/equationnest.xml index 11283e762..7b5a75344 100644 --- a/t/complex/equationnest.xml +++ b/t/complex/equationnest.xml @@ -200,8 +200,7 @@ - -and so on. + and so on. @@ -234,8 +233,7 @@ and so on. - -and so on. + and so on. diff --git a/t/digestion/dollar.xml b/t/digestion/dollar.xml index d2d284178..4771753cc 100644 --- a/t/digestion/dollar.xml +++ b/t/digestion/dollar.xml @@ -34,7 +34,7 @@ Another m - t
Spacing - -Word + Spacing + Word Word Word Word Word -OK
Spacing
OK
Funny Characters - -a b $ c & d # e ^ f _ g % h ~ i -
Funny Characters
With math? - -a+b - - - + - a - b - - - a+b -
With math?
Blank lines - -Word + Blank lines + Word Word @@ -48,6 +44,6 @@ Word Word Word -
Blank lines
+ @@ -100,7 +100,7 @@ Testing and inside . - + Algorithm 1 1 diff --git a/t/fonts/sizes.xml b/t/fonts/sizes.xml index 3f38097e2..84742c1db 100644 --- a/t/fonts/sizes.xml +++ b/t/fonts/sizes.xml @@ -181,7 +181,7 @@ Text: ‘ hop - ’ is 469.75499pt x 6.94444pt + 1.94444pt. + ’ is 345.0pt x 6.94444pt + 1.94444pt. Text: ‘ diff --git a/t/structure/autoref.xml b/t/structure/autoref.xml index 6bc14ec2a..3d3d6e2e9 100644 --- a/t/structure/autoref.xml +++ b/t/structure/autoref.xml @@ -60,7 +60,7 @@ Table 1.1Table caption The Table - + Figure 1.2 Figure 1.2 diff --git a/t/structure/figure_grids.xml b/t/structure/figure_grids.xml index a51e090dd..724f16070 100644 --- a/t/structure/figure_grids.xml +++ b/t/structure/figure_grids.xml @@ -179,7 +179,7 @@ (a) 1(a) - + (a)subcaption (a)subcaption @@ -188,7 +188,7 @@ (b) 1(b) - + (b)subcaption (b)subcaption @@ -201,7 +201,7 @@ (a) 2(a) - + (a)subcaption (a)subcaption @@ -211,7 +211,7 @@ (b) 2(b) - + (b)subcaption (b)subcaption @@ -224,7 +224,7 @@ (a) 3(a) - + (a)subcaption (a)subcaption @@ -233,7 +233,7 @@ (b) 3(b) - + (b)subcaption (b)subcaption @@ -243,7 +243,7 @@ (c) 3(c) - + (c)subcaption (c)subcaption @@ -252,7 +252,7 @@ (d) 3(d) - + (d)subcaption (d)subcaption @@ -265,7 +265,7 @@ (a) 4(a) - + (a)subcaption (a)subcaption @@ -274,7 +274,7 @@ (b) 4(b) - + (b)subcaption (b)subcaption @@ -283,7 +283,7 @@ (c) 4(c) - + (c)subcaption (c)subcaption @@ -293,7 +293,7 @@ (d) 4(d) - + (d)subcaption (d)subcaption @@ -302,7 +302,7 @@ (e) 4(e) - + (e)subcaption (e)subcaption @@ -311,7 +311,7 @@ (f) 4(f) - + (f)subcaption (f)subcaption @@ -321,7 +321,7 @@ (g) 4(g) - + (g)subcaption (g)subcaption @@ -330,7 +330,7 @@ (h) 4(h) - + (h)subcaption (h)subcaption @@ -339,7 +339,7 @@ (i) 4(i) - + (i)subcaption (i)subcaption @@ -352,7 +352,7 @@ (a) 5(a) - + (a)subcaption (a)subcaption @@ -361,7 +361,7 @@ (b) 5(b) - + (b)subcaption (b)subcaption @@ -370,7 +370,7 @@ (c) 5(c) - + (c)subcaption (c)subcaption @@ -379,7 +379,7 @@ (d) 5(d) - + (d)subcaption (d)subcaption @@ -389,7 +389,7 @@ (e) 5(e) - + (e)subcaption (e)subcaption @@ -398,7 +398,7 @@ (f) 5(f) - + (f)subcaption (f)subcaption @@ -407,7 +407,7 @@ (g) 5(g) - + (g)subcaption (g)subcaption @@ -416,7 +416,7 @@ (h) 5(h) - + (h)subcaption (h)subcaption @@ -426,7 +426,7 @@ (i) 5(i) - + (i)subcaption (i)subcaption @@ -435,7 +435,7 @@ (j) 5(j) - + (j)subcaption (j)subcaption @@ -444,7 +444,7 @@ (k) 5(k) - + (k)subcaption (k)subcaption @@ -453,7 +453,7 @@ (l) 5(l) - + (l)subcaption (l)subcaption @@ -463,7 +463,7 @@ (m) 5(m) - + (m)subcaption (m)subcaption @@ -472,7 +472,7 @@ (n) 5(n) - + (n)subcaption (n)subcaption @@ -481,7 +481,7 @@ (o) 5(o) - + (o)subcaption (o)subcaption @@ -490,7 +490,7 @@ (p) 5(p) - + (p)subcaption (p)subcaption @@ -503,7 +503,7 @@ (a) 6(a) - + (a)subcaption (a)subcaption @@ -512,7 +512,7 @@ (b) 6(b) - + (b)subcaption (b)subcaption @@ -521,7 +521,7 @@ (c) 6(c) - + (c)subcaption (c)subcaption @@ -530,7 +530,7 @@ (d) 6(d) - + (d)subcaption (d)subcaption @@ -540,7 +540,7 @@ (e) 6(e) - + (e)subcaption (e)subcaption @@ -549,7 +549,7 @@ (f) 6(f) - + (f)subcaption (f)subcaption @@ -558,7 +558,7 @@ (g) 6(g) - + (g)subcaption (g)subcaption @@ -567,7 +567,7 @@ (h) 6(h) - + (h)subcaption (h)subcaption @@ -580,7 +580,7 @@ (a) 7(a) - + (a)subcaption (a)subcaption @@ -589,7 +589,7 @@ (b) 7(b) - + (b)subcaption (b)subcaption @@ -599,7 +599,7 @@ (c) 7(c) - + (c)subcaption (c)subcaption @@ -608,7 +608,7 @@ (d) 7(d) - + (d)subcaption (d)subcaption @@ -618,7 +618,7 @@ (e) 7(e) - + (e)subcaption (e)subcaption @@ -627,7 +627,7 @@ (f) 7(f) - + (f)subcaption (f)subcaption @@ -637,7 +637,7 @@ (g) 7(g) - + (g)subcaption (g)subcaption @@ -646,7 +646,7 @@ (h) 7(h) - + (h)subcaption (h)subcaption @@ -659,7 +659,7 @@ (a) 8(a) - + (a)subcaption (a)subcaption @@ -668,7 +668,7 @@ (b) 8(b) - + (b)subcaption (b)subcaption @@ -677,7 +677,7 @@ (c) 8(c) - + (c)subcaption (c)subcaption @@ -686,7 +686,7 @@ (d) 8(d) - + (d)subcaption (d)subcaption @@ -695,7 +695,7 @@ (e) 8(e) - + (e)subcaption (e)subcaption @@ -705,7 +705,7 @@ (f) 8(f) - + (f)subcaption (f)subcaption @@ -714,7 +714,7 @@ (g) 8(g) - + (g)subcaption (g)subcaption @@ -723,7 +723,7 @@ (h) 8(h) - + (h)subcaption (h)subcaption @@ -732,7 +732,7 @@ (i) 8(i) - + (i)subcaption (i)subcaption @@ -741,7 +741,7 @@ (j) 8(j) - + (j)subcaption (j)subcaption @@ -751,7 +751,7 @@ (k) 8(k) - + (k)subcaption (k)subcaption @@ -760,7 +760,7 @@ (l) 8(l) - + (l)subcaption (l)subcaption @@ -769,7 +769,7 @@ (m) 8(m) - + (m)subcaption (m)subcaption @@ -778,7 +778,7 @@ (n) 8(n) - + (n)subcaption (n)subcaption @@ -787,7 +787,7 @@ (o) 8(o) - + (o)subcaption (o)subcaption @@ -797,7 +797,7 @@ (p) 8(p) - + (p)subcaption (p)subcaption @@ -806,7 +806,7 @@ (q) 8(q) - + (q)subcaption (q)subcaption @@ -815,7 +815,7 @@ (r) 8(r) - + (r)subcaption (r)subcaption @@ -824,7 +824,7 @@ (s) 8(s) - + (s)subcaption (s)subcaption @@ -833,7 +833,7 @@ (t) 8(t) - + (t)subcaption (t)subcaption @@ -843,7 +843,7 @@ (u) 8(u) - + (u)subcaption (u)subcaption @@ -852,7 +852,7 @@ (v) 8(v) - + (v)subcaption (v)subcaption @@ -861,7 +861,7 @@ (w) 8(w) - + (w)subcaption (w)subcaption @@ -870,7 +870,7 @@ (x) 8(x) - + (x)subcaption (x)subcaption @@ -879,7 +879,7 @@ (y) 8(y) - + (y)subcaption (y)subcaption @@ -892,7 +892,7 @@ (a) 9(a) - + (a)subcaption (a)subcaption @@ -901,7 +901,7 @@ (b) 9(b) - + (b)subcaption (b)subcaption @@ -910,7 +910,7 @@ (c) 9(c) - + (c)subcaption (c)subcaption @@ -919,7 +919,7 @@ (d) 9(d) - + (d)subcaption (d)subcaption @@ -928,7 +928,7 @@ (e) 9(e) - + (e)subcaption (e)subcaption @@ -937,7 +937,7 @@ (f) 9(f) - + (f)subcaption (f)subcaption @@ -947,7 +947,7 @@ (g) 9(g) - + (g)subcaption (g)subcaption @@ -956,7 +956,7 @@ (h) 9(h) - + (h)subcaption (h)subcaption @@ -965,7 +965,7 @@ (i) 9(i) - + (i)subcaption (i)subcaption @@ -974,7 +974,7 @@ (j) 9(j) - + (j)subcaption (j)subcaption @@ -983,7 +983,7 @@ (k) 9(k) - + (k)subcaption (k)subcaption @@ -992,7 +992,7 @@ (l) 9(l) - + (l)subcaption (l)subcaption @@ -1002,7 +1002,7 @@ (m) 9(m) - + (m)subcaption (m)subcaption @@ -1011,7 +1011,7 @@ (n) 9(n) - + (n)subcaption (n)subcaption @@ -1020,7 +1020,7 @@ (o) 9(o) - + (o)subcaption (o)subcaption @@ -1029,7 +1029,7 @@ (p) 9(p) - + (p)subcaption (p)subcaption @@ -1038,7 +1038,7 @@ (q) 9(q) - + (q)subcaption (q)subcaption @@ -1047,7 +1047,7 @@ (r) 9(r) - + (r)subcaption (r)subcaption @@ -1057,7 +1057,7 @@ (s) 9(s) - + (s)subcaption (s)subcaption @@ -1066,7 +1066,7 @@ (t) 9(t) - + (t)subcaption (t)subcaption @@ -1075,7 +1075,7 @@ (u) 9(u) - + (u)subcaption (u)subcaption @@ -1084,7 +1084,7 @@ (v) 9(v) - + (v)subcaption (v)subcaption @@ -1093,7 +1093,7 @@ (w) 9(w) - + (w)subcaption (w)subcaption @@ -1102,7 +1102,7 @@ (x) 9(x) - + (x)subcaption (x)subcaption @@ -1112,7 +1112,7 @@ (y) 9(y) - + (y)subcaption (y)subcaption @@ -1121,7 +1121,7 @@ (z) 9(z) - + (z)subcaption (z)subcaption @@ -1130,7 +1130,7 @@ (aa) 9(aa) - + (aa)subcaption (aa)subcaption @@ -1139,7 +1139,7 @@ (ab) 9(ab) - + (ab)subcaption (ab)subcaption @@ -1148,7 +1148,7 @@ (ac) 9(ac) - + (ac)subcaption (ac)subcaption @@ -1157,7 +1157,7 @@ (ad) 9(ad) - + (ad)subcaption (ad)subcaption @@ -1167,7 +1167,7 @@ (ae) 9(ae) - + (ae)subcaption (ae)subcaption @@ -1176,7 +1176,7 @@ (af) 9(af) - + (af)subcaption (af)subcaption @@ -1185,7 +1185,7 @@ (ag) 9(ag) - + (ag)subcaption (ag)subcaption @@ -1194,7 +1194,7 @@ (ah) 9(ah) - + (ah)subcaption (ah)subcaption @@ -1203,7 +1203,7 @@ (ai) 9(ai) - + (ai)subcaption (ai)subcaption @@ -1212,7 +1212,7 @@ (aj) 9(aj) - + (aj)subcaption (aj)subcaption From 423ed3a62bc802374eac1275d298e961137bf22c Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Wed, 29 Apr 2026 17:50:29 -0400 Subject: [PATCH 07/31] Make sure arg of \underline,\overline are in restricted_horizontal; adjust size to account for lines --- lib/LaTeXML/Engine/TeX_Math.pool.ltxml | 22 ++++++++++++++----- .../Engine/latex_constructs.pool.ltxml | 6 ++--- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/lib/LaTeXML/Engine/TeX_Math.pool.ltxml b/lib/LaTeXML/Engine/TeX_Math.pool.ltxml index c18246607..2f81ba7a4 100644 --- a/lib/LaTeXML/Engine/TeX_Math.pool.ltxml +++ b/lib/LaTeXML/Engine/TeX_Math.pool.ltxml @@ -300,7 +300,7 @@ sub cleanup_XMText { $document->replaceNode($m, map { $_->childNodes } $m->childNodes); } else { # Otherwise, wrap whatever it is in an XMText $document->wrapNodes('ltx:XMText', $m); } - } } } + } } } # And now we don't need the XMText any more. foreach my $attr ($textnode->attributes) { # Copy the child's attributes (should Merge!!) $table->setAttribute($attr->nodeName => $attr->getValue); } @@ -423,7 +423,7 @@ sub scriptHandler { my $c = (($op eq 'SUPERSCRIPT') ? '^' : '_'); Error('unexpected', $c, $stomach, "Script $c can only appear in math mode"); return Box($c, undef, undef, (($op eq 'SUPERSCRIPT') ? T_SUPER : T_SUB)); - } } +} } DefPrimitiveI(T_SUPER, undef, sub { scriptHandler($_[0], 'SUPERSCRIPT'); }); DefPrimitiveI(T_SUB, undef, sub { scriptHandler($_[0], 'SUBSCRIPT'); }); @@ -544,7 +544,7 @@ DefRewrite(xpath => 'descendant::ltx:Math[child::ltx:XMath[child::ltx:XMApp[' . elsif ($role eq 'FLOATSUBSCRIPT') { $document->insertElement('ltx:sub', $text); return; } - } } } } + } } } } # should never happen, but just in case: Info("rewrite", "footnotemark", "Failed to find floating node in: " . $math->toString(1)); $document->getNode->appendChild($math); @@ -819,7 +819,7 @@ sub augmentDelimiterProperties { if (exists $$entry{char}) { # replace the char content! if (my $char = $$entry{char}) { $delim->firstChild->setData($char); } } - } } + } } return; } # Useful for afterConstruct of delimiter sizing macros (eg. \bigl) @@ -953,13 +953,23 @@ DefMath('\lx@math@overline{}', UTF(0xAF), name => 'overline', alias => '\overline'); DefConstructor('\lx@text@overline{}', "#1", - enterHorizontal => 1); + enterHorizontal => 1, mode => 'restricted_horizontal', + sizer => sub { + my $text = $_[0]->getArg(1); + my ($w, $h, $d) = $text->getSize; + my $pad = Dimension($text->getFont->getEXHeight); # WAG + return ($w, $h->add($pad), $d); }); DefMath('\lx@math@underline{}', UTF(0xAF), operator_role => 'UNDERACCENT', operator_stretchy => 'true', name => 'underline', alias => '\underline'); DefConstructor('\lx@text@underline{}', "#1", - enterHorizontal => 1); + enterHorizontal => 1, mode => 'restricted_horizontal', + sizer => sub { + my $text = $_[0]->getArg(1); + my ($w, $h, $d) = $text->getSize; + my $pad = Dimension($text->getFont->getEXHeight); # WAG + return ($w, $h, $d->add($pad)); }); DefMath('\lx@math@overrightarrow{}', "\x{2192}", operator_role => 'OVERACCENT', operator_stretchy => 'true', name => 'overrightarrow', alias => '\overrightarrow'); diff --git a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml index 98497a144..d1a4f234b 100644 --- a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml +++ b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml @@ -1842,7 +1842,7 @@ DefConstructor('\@internal@math@verb{} Undigested {}', DefConstructor('\@internal@text@verb{} Undigested {}', "#3", font => { family => 'typewriter', series => 'medium', shape => 'upright' }, - enterHorizontal => 1, + mode => 'restricted_horizontal', enterHorizontal => 1, beforeConstruct => sub { my ($doc, $whatsit) = @_; $doc->canContain($doc->getElement, '#PCDATA') || $doc->openElement('ltx:p'); }, @@ -4182,8 +4182,8 @@ DefConstructor('\@@cite []{}', "#2#3#4", - enterHorizontal => 1, - properties => sub { (bibrefs => CleanBibKey($_[2]), + mode => 'restricted_horizontal', enterHorizontal => 1, + properties => sub { (bibrefs => CleanBibKey($_[2]), separator => DigestText(LookupValue('CITE_SEPARATOR')), yyseparator => DigestText(LookupValue('CITE_YY_SEPARATOR')), bibunit => LookupValue('CITE_UNIT')); }); From 2bae3da85de535120350c2e4e6d00c3d2cf95ce8 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Wed, 29 Apr 2026 17:52:41 -0400 Subject: [PATCH 08/31] Make ltx_verbatim nowrap; hack fancyvrb to use that css class --- lib/LaTeXML/Package/fancyvrb.sty.ltxml | 4 ++++ lib/LaTeXML/resources/CSS/LaTeXML.css | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/LaTeXML/Package/fancyvrb.sty.ltxml b/lib/LaTeXML/Package/fancyvrb.sty.ltxml index e414f0bcc..69e3afdac 100644 --- a/lib/LaTeXML/Package/fancyvrb.sty.ltxml +++ b/lib/LaTeXML/Package/fancyvrb.sty.ltxml @@ -19,6 +19,10 @@ use LaTeXML::Package; InputDefinitions('fancyvrb', type => 'sty', noltxml => 1); +# Hack internals to add css class ltx_nowrap +Let('\lx@save@FancyVerbFormatLine', '\FancyVerbFormatLine'); +DefMacro('\FancyVerbFormatLine{}', + '\@ADDCLASS{ltx_verbatim}\lx@save@FancyVerbFormatLine{#1}'); #********************************************************************** 1; diff --git a/lib/LaTeXML/resources/CSS/LaTeXML.css b/lib/LaTeXML/resources/CSS/LaTeXML.css index 5efd8d018..e371a1737 100644 --- a/lib/LaTeXML/resources/CSS/LaTeXML.css +++ b/lib/LaTeXML/resources/CSS/LaTeXML.css @@ -445,7 +445,7 @@ span.ltx_framed { display:inline-block; text-indent:0; } /* avoid padding/ /*====================================================================== Misc */ /* .ltx_verbatim*/ -.ltx_verbatim { text-align:left; } +.ltx_verbatim { text-align:left; white-space:nowrap; } /*====================================================================== Meta stuff, footnotes */ .ltx_note_content { display:none; } From 251a24bc0ffd833d3b7932761abbb5eef2290d68 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 30 Apr 2026 18:04:33 -0400 Subject: [PATCH 09/31] Make \lx@tag and friends compute their size --- lib/LaTeXML/Engine/Base_Utility.pool.ltxml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/LaTeXML/Engine/Base_Utility.pool.ltxml b/lib/LaTeXML/Engine/Base_Utility.pool.ltxml index 65951b05d..bbb196daa 100644 --- a/lib/LaTeXML/Engine/Base_Utility.pool.ltxml +++ b/lib/LaTeXML/Engine/Base_Utility.pool.ltxml @@ -994,17 +994,22 @@ sub removeEmptyElement { DefConstructor('\lx@tag[][][]{}', "#4", mode => 'restricted_horizontal', + sizer => '#4', afterConstruct => \&removeEmptyElement); # \lx@tag@intags{role}{stuff} DefConstructor('\lx@tag@intags[]{}', "#2", mode => 'restricted_horizontal', + sizer => '#2', beforeDigest => sub { neutralizeFont() }, afterConstruct => \&removeEmptyElement); DefConstructor('\lx@tags{}', "#1", + sizer => sub { + my @tags = $_[0]->getArg(1)->unlist; # Arbitarily, only size the 1st ltx:tag. + ($tags[0] ? $tags[0]->getSize : (Dimension(0), Dimension(0), Dimension(0))); }, afterConstruct => \&removeEmptyElement); #---------------------------------------------------------------------- From daa9dbfeb64a5e97ca23e4c386b97ff3a3277e08 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 30 Apr 2026 18:05:22 -0400 Subject: [PATCH 10/31] soul's commands should process their arg in restricted_horizontal --- lib/LaTeXML/Package/soul.sty.ltxml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/LaTeXML/Package/soul.sty.ltxml b/lib/LaTeXML/Package/soul.sty.ltxml index 4fc1bf4ed..f6087dc08 100644 --- a/lib/LaTeXML/Package/soul.sty.ltxml +++ b/lib/LaTeXML/Package/soul.sty.ltxml @@ -68,7 +68,7 @@ sub getSOULcolor { # (should set framecolor from \setulcolor) DefConstructor('\textul{}', "#1", - enterHorizontal => 1, + enterHorizontal => 1, mode => 'restricted_horizontal', properties => { framecolor => sub { getSOULcolor('soul_ul_color'); } }); # Customizing underlines @@ -85,7 +85,7 @@ DefMacro('\setuloverlap{Dimension}', undef); # but then how to make framecolor end up as text-decoration-color? DefConstructor('\textst{}', "#1", - enterHorizontal => 1, + enterHorizontal => 1, mode => 'restricted_horizontal', properties => { framecolor => sub { my $framecolor = getSOULcolor('soul_strike_color'); return $framecolor ? "text-decoration-color:" . ($framecolor->toAttribute()) . ";" : ""; } }); @@ -97,7 +97,7 @@ DefPrimitive('\setstcolor{}', sub { AssignValue(soul_strike_color => $_[1]); }); DefMacro('\texthl', '\@ifpackageloaded{color}{\lx@texthl@color}{\textul}'); DefConstructor('\lx@texthl@color{}', "#1", - enterHorizontal => 1, + enterHorizontal => 1, mode => 'restricted_horizontal', bounded => 1, beforeDigest => sub { MergeFont(background => getSOULcolor('soul_hl_color')); }); # Customizing highlight AssignValue(soul_hl_color => 'yellow'); From 58736fe493488abeacda1543430b6ef286271628 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 30 Apr 2026 18:29:16 -0400 Subject: [PATCH 11/31] Make sure captions arg processed as paragraph mode (horizontal), and have reasonable sizing --- lib/LaTeXML/Engine/latex_constructs.pool.ltxml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml index d1a4f234b..3f57de830 100644 --- a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml +++ b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml @@ -3358,11 +3358,25 @@ Tag('ltx:table', afterClose => sub { BuildPanelsAndID(@_, 'tab'); }); # These may need to float up to where they're allowed, # or they may need to close or similar. +# These (should) appear in an internal_vertical context, +# BUT the arg should be processed to a horizontal (paragraph) List! DefConstructor('\@@caption{}', "^^#1", - sizer => 0, mode => 'restricted_horizontal', + sizer => '#1', + beforeDigest => sub { + $_[0]->beginMode('internal_vertical'); + $_[0]->enterHorizontal; }, + afterDigest => sub { + $_[0]->leaveHorizontal_internal; + $_[0]->endMode('internal_vertical') }, ); DefConstructor('\@@toccaption{}', "^^#1", - sizer => 0, mode => 'restricted_horizontal', + sizer => 0, + beforeDigest => sub { + $_[0]->beginMode('internal_vertical'); + $_[0]->enterHorizontal; }, + afterDigest => sub { + $_[0]->leaveHorizontal_internal; + $_[0]->endMode('internal_vertical') }, ); sub beforeFloat { From 1abcf815805470bf964fb87a9d9a03757183123e Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 30 Apr 2026 18:29:58 -0400 Subject: [PATCH 12/31] Update tests for caption change --- t/graphics/graphrot.xml | 2 +- t/structure/figures.xml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/t/graphics/graphrot.xml b/t/graphics/graphrot.xml index b4df83285..9cb6f16ea 100644 --- a/t/graphics/graphrot.xml +++ b/t/graphics/graphrot.xml @@ -395,7 +395,7 @@ the whales Save the whale Save the + Table 5 Table 5 diff --git a/t/structure/figures.xml b/t/structure/figures.xml index ad3567cbe..c4a990cce 100644 --- a/t/structure/figures.xml +++ b/t/structure/figures.xml @@ -52,6 +52,7 @@ (a)This is a figure (a)This is a figure + (b) @@ -105,6 +106,7 @@ and collectively as . (a)This is a figure (a)This is a figure + (b) @@ -133,6 +135,7 @@ and collectively as . (a)This is a table [Tabular One] + (b) @@ -188,6 +191,7 @@ and collectively as . (a)This is a table [Tabular One] + (b) From 53489cbb12804364d0ca93f81f5969c66817941c Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 30 Apr 2026 18:50:30 -0400 Subject: [PATCH 13/31] Extract internals of stomach->digestNextBody to stomach->digestUntil which does NOT rebind LaTeXML::LIST, which is needed for digesting T_BEGIN --- lib/LaTeXML/Core/Stomach.pm | 15 ++++++++++++--- lib/LaTeXML/Engine/TeX_Box.pool.ltxml | 17 +++++++++-------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/lib/LaTeXML/Core/Stomach.pm b/lib/LaTeXML/Core/Stomach.pm index dfed00105..a5b5be499 100644 --- a/lib/LaTeXML/Core/Stomach.pm +++ b/lib/LaTeXML/Core/Stomach.pm @@ -90,12 +90,21 @@ sub getScriptLevel { # It puts a lot of cruft in Gullet; Should we just create a new Gullet? sub digestNextBody { + my ($self, $terminal) = @_; + no warnings 'recursion'; + local @LaTeXML::LIST = (); + digestUntil($self, $terminal); + return @LaTeXML::LIST; } + +# This method digests content until $terminal or closing initial mode, +# pushing onto @LaTeXML::LIST. +# But, unlike digestNextBody, it does NOT bind @LaTeXML::LIST, nor return anything. +sub digestUntil { my ($self, $terminal) = @_; no warnings 'recursion'; my $startloc = getLocator($self); my $initdepth = scalar(@{ $$self{boxing} }); my $token; - local @LaTeXML::LIST = (); my $alignment = $STATE->lookupValue('Alignment'); my @aug = (); @@ -108,7 +117,7 @@ sub digestNextBody { # at least \over calls in here without the intent to passing through the alignment. # So if we already have some digested boxes available, return them here. $$self{gullet}->unread($token); - return @LaTeXML::LIST; } + return; } my @r = invokeToken($self, $token); push(@LaTeXML::LIST, @r); push(@aug, $token, @r); @@ -119,7 +128,7 @@ sub digestNextBody { "Got " . join("\n -- ", map { Stringify($_) } @aug)) if $terminal && !Equals($token, $terminal); push(@LaTeXML::LIST, Box()) unless $token; # Dummy `trailer' if none explicit. - return @LaTeXML::LIST; } + return; } # Digest a list of tokens independent from any current Gullet. # Typically used to digest arguments to primitives or constructors. diff --git a/lib/LaTeXML/Engine/TeX_Box.pool.ltxml b/lib/LaTeXML/Engine/TeX_Box.pool.ltxml index 496dfb06b..11fa3ab1d 100644 --- a/lib/LaTeXML/Engine/TeX_Box.pool.ltxml +++ b/lib/LaTeXML/Engine/TeX_Box.pool.ltxml @@ -27,18 +27,19 @@ $LaTeXML::DEBUG{svg} = 1 if $$LaTeXML::DEBUG{svg_verbose}; # These are actually TeX primitives, but we treat them as a Whatsit so they # remain in the constructed tree. -#DefConstructor('{','#body', beforeDigest=>sub{$_[0]->bgroup;}, captureBody=>1); -######DefPrimitive('{', sub { DefPrimitive(T_BEGIN, sub { my ($stomach) = @_; $stomach->bgroup; - my $open = Box(undef, undef, undef, T_BEGIN, isEmpty => 1, alignmentSkippable => 1); - my $ismath = $STATE->lookupValue('IN_MATH'); - my $mode = $STATE->lookupValue('MODE'); - my @body = $stomach->digestNextBody(); - return ($ismath ? List($open, @body, mode => $mode) : ($open, @body)); }); + my $open = Box(undef, undef, undef, T_BEGIN, isEmpty => 1, alignmentSkippable => 1); + my $mode = $STATE->lookupValue('MODE'); + if ($STATE->lookupValue('IN_MATH')) { # In math, we return a Math List + my @body = $stomach->digestNextBody(); + return List($open, @body, mode => $mode); } + else { # Else just digest directly to @LaTeXML::LIST + push(@LaTeXML::LIST, $open); + $stomach->digestUntil(); + return; } }); -#######DefPrimitive('}', sub { DefPrimitive(T_END, sub { my $f = LookupValue('font'); $_[0]->egroup; From 3cc1eb9fbaa95d77e5f34863ac85f7b2911412ec Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 7 May 2026 16:23:15 -0400 Subject: [PATCH 14/31] minipage defaults to justify alignment --- lib/LaTeXML/resources/CSS/LaTeXML.css | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/LaTeXML/resources/CSS/LaTeXML.css b/lib/LaTeXML/resources/CSS/LaTeXML.css index e371a1737..4f4578337 100644 --- a/lib/LaTeXML/resources/CSS/LaTeXML.css +++ b/lib/LaTeXML/resources/CSS/LaTeXML.css @@ -562,6 +562,7 @@ cite { font-style: normal; } .ltx_minipage { align-self: normal; display: inline-block; + text-align: justify; } .ltx_minipage > .ltx_graphics { max-width:100%; From 8835f238360041ab13476c2e87268466a2b6f0cb Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 7 May 2026 16:24:08 -0400 Subject: [PATCH 15/31] Pass context as block for svg:foreignObject to avoid bad span soup --- lib/LaTeXML/resources/XSLT/LaTeXML-picture-xhtml.xsl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/LaTeXML/resources/XSLT/LaTeXML-picture-xhtml.xsl b/lib/LaTeXML/resources/XSLT/LaTeXML-picture-xhtml.xsl index 1c5400006..45dfda7a0 100644 --- a/lib/LaTeXML/resources/XSLT/LaTeXML-picture-xhtml.xsl +++ b/lib/LaTeXML/resources/XSLT/LaTeXML-picture-xhtml.xsl @@ -169,8 +169,9 @@ ltx_foreignobject_container ltx_foreignobject_content - - + + + From 7ef75c214f5ba5d274628efd4628747c497ece80 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 7 May 2026 16:26:01 -0400 Subject: [PATCH 16/31] Define methods allowing \multicolumn to REPLACE the column spec, so it gets all properties --- lib/LaTeXML/Core/Alignment.pm | 12 ++++++++++-- lib/LaTeXML/Core/Alignment/Template.pm | 9 +++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/LaTeXML/Core/Alignment.pm b/lib/LaTeXML/Core/Alignment.pm index aeccb97d6..830200e51 100644 --- a/lib/LaTeXML/Core/Alignment.pm +++ b/lib/LaTeXML/Core/Alignment.pm @@ -21,8 +21,8 @@ use LaTeXML::Common::XML; use LaTeXML::Common::Dimension; use LaTeXML::Core::Alignment::Template; use List::Util qw(max sum); -use base qw(LaTeXML::Core::Whatsit); -use base qw(Exporter); +use base qw(LaTeXML::Core::Whatsit); +use base qw(Exporter); our @EXPORT = (qw( &ReadAlignmentTemplate &MatrixTemplate)); @@ -165,6 +165,14 @@ sub currentColumn { my ($self) = @_; return $$self{current_row} && $$self{current_row}->column($$self{current_column}); } +# Used by \multicolumn to replace the current column spec with a new one +# (the row has been cloned from the overall spec) +sub replaceColumn { + my ($self, $colspec) = @_; + $$self{current_row}->replaceColumn($$self{current_column}, $colspec) + if $$self{current_row}; + return; } + sub getColumn { my ($self, $n) = @_; return $$self{current_row} && $$self{current_row}->column($n); } diff --git a/lib/LaTeXML/Core/Alignment/Template.pm b/lib/LaTeXML/Core/Alignment/Template.pm index 16cd7d889..34caa900f 100644 --- a/lib/LaTeXML/Core/Alignment/Template.pm +++ b/lib/LaTeXML/Core/Alignment/Template.pm @@ -150,6 +150,15 @@ sub column { push(@{ $$self{columns} }, {%dup}); } } } return ($n > 0 ? $$self{columns}->[$n - 1] : undef); } +# Used by \multicolumn to replace column $n spec with a new one +# (the row has been cloned from the overall spec) +sub replaceColumn { + my ($self, $n, $colspec) = @_; + my $N = scalar(@{ $$self{columns} }); + if ($n <= $N) { + $$self{columns}->[$n] = $colspec; } + return; } + sub columns { my ($self) = @_; return @{ $$self{columns} }; } From 8fb9c8b07e0fe81d847844952e347efd4b269268 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 7 May 2026 16:34:13 -0400 Subject: [PATCH 17/31] FIx column specifiers p,m,b to use \vbox so size computed correctly; Make \multicolumn copy the new spec into the alignment, so properties preserved; when storing column data get data from correct column when it was spanned --- lib/LaTeXML/Engine/TeX_Tables.pool.ltxml | 59 +++++++++++++++--------- lib/LaTeXML/Package/array.sty.ltxml | 22 +++++---- 2 files changed, 51 insertions(+), 30 deletions(-) diff --git a/lib/LaTeXML/Engine/TeX_Tables.pool.ltxml b/lib/LaTeXML/Engine/TeX_Tables.pool.ltxml index f97fa9402..9197571f1 100644 --- a/lib/LaTeXML/Engine/TeX_Tables.pool.ltxml +++ b/lib/LaTeXML/Engine/TeX_Tables.pool.ltxml @@ -66,12 +66,11 @@ DefColumnType('r', sub { DefColumnType('p{Dimension}', sub { $LaTeXML::BUILD_TEMPLATE->addColumn( - before => Tokens(T_CS('\vtop'), T_BEGIN, T_CS('\hbox'), - T_LETTER('t'), T_LETTER('o'), $_[1]->revert, T_CS('\relax'), - T_BEGIN), + before => Tokens(T_CS('\vtop'), T_BEGIN, T_BEGIN, + T_CS('\hsize'), $_[1]->revert, T_CS('\relax')), after => Tokens(T_END, T_END), vattach => 'top', - align => 'justify', + align => 'justify', width => $_[1], ); return; }); DefColumnType('*{Number}{}', sub { @@ -170,7 +169,7 @@ DefConstructor('\halign BoxSpecification', beforeConstruct => sub { my ($document) = @_; $document->maybeCloseElement('ltx:p'); }, - afterDigest => sub { # After spec, but before body! + afterDigest => sub { # After spec, but before body! my ($stomach, $whatsit) = @_; $whatsit->setProperty(mode => 'internal_vertical'); $stomach->beginMode('restricted_horizontal'); @@ -360,7 +359,7 @@ sub digestAlignmentColumn { my ($stomach, $alignment, $lastwascr) = @_; my $gullet = $stomach->getGullet; my $ismath = $STATE->lookupValue('IN_MATH'); - my $mode = $STATE->lookupValue('MODE'); + my $mode = $STATE->lookupValue('MODE'); local @LaTeXML::LIST = (); # Scan for leading \omit, skipping over (& saving) \hline. Debug("Halign $alignment: COLUMN starting scan ($mode)") if $LaTeXML::DEBUG{halign}; @@ -456,16 +455,19 @@ sub trimColumnTemplate { # Given the boxes for an alignment cell, # extract & remove the various fills and rules from the ends to annotate the cell structure +# Note: With spanned columns (\multicolumn) \omit has moved to the LAST column of span, +# but we want to store data in the FIRST column, to be consistent with rowspan. sub extractAlignmentColumn { my ($alignment, $boxes) = @_; return () unless $alignment; # ?? # Note: $n0,$n1 is a VERY round-about way of tracking the column spanning! - my $ismath = $STATE->lookupValue('IN_MATH'); - my $n0 = (LookupValue('alignmentStartColumn') || 0) + 1; - my $n1 = $alignment->currentColumnNumber; - my $colspec = $alignment->getColumn($n0); - my $align = $$colspec{align} || 'left'; - my $border = ''; + my $ismath = $STATE->lookupValue('IN_MATH'); + my $n0 = (LookupValue('alignmentStartColumn') || 0) + 1; + my $n1 = $alignment->currentColumnNumber; + my $colspec0 = $alignment->getColumn($n0); # Where we'll PUT the info + my $colspec1 = $alignment->getColumn($n1); # But this is the spec USED + my $align = $$colspec1{align} || 'left'; + my $border = ''; # Peel off any boxes from both sides until we get the "meat" of the column. # from this we can establish borders, alignment and emptiness. # But we, of course, immediately put them back... @@ -473,7 +475,7 @@ sub extractAlignmentColumn { my @saveleft = (); my @saveright = (); my (@lspaces, @rspaces); - if (my $skip = $$colspec{tabskip}) { + if (my $skip = $$colspec1{tabskip}) { push(@lspaces, Digest(Tokens(T_CS('\hskip'), $skip->revert, T_CS('\relax')))); } while (@boxes) { if (ref $boxes[0] eq 'LaTeXML::Core::List') { @@ -515,24 +517,26 @@ sub extractAlignmentColumn { unshift(@saveright, pop(@boxes)); } else { last; } } - delete $$colspec{width} unless $align eq 'justify'; + delete $$colspec0{width} unless $align eq 'justify'; # Replacing boxes with the fil padding & vertical rules stripped off @boxes = (@saveleft, @boxes, @saveright); $boxes = List(@boxes, mode => $boxes->getProperty('mode')); # record relevant info in the Alignment. - $$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; + $$colspec0{align} = $align; + $$colspec0{width} = $$colspec1{width}; + $$colspec0{border} = $border = ($$colspec1{border} || '') . $border; + $$colspec0{boxes} = $boxes; + $$colspec0{lspaces} = List(@lspaces) if @lspaces; + $$colspec0{rspaces} = List(@rspaces) if @rspaces; + $$colspec0{colspan} = $n1 - $n0 + 1; if ($$alignment{in_tabular_head} || $$alignment{in_tabular_foot}) { - $$colspec{thead}{column} = 1; } + $$colspec0{thead}{column} = 1; } + for (my $i = $n0 + 1 ; $i <= $n1 ; $i++) { my $c = $alignment->getColumn($i); $$c{skipped} = 1 if $c; } - Debug("Halign $alignment: INSTALL column " . join(',', map { $_ . "=" . ToString($$colspec{$_}); } sort keys %$colspec)) if $LaTeXML::DEBUG{halign}; + Debug("Halign $alignment: INSTALL column " . join(',', map { $_ . "=" . ToString($$colspec0{$_}); } sort keys %$colspec0)) if $LaTeXML::DEBUG{halign}; return $boxes; } #====================================================================== @@ -691,6 +695,17 @@ DefMacro('\lx@alignment@multicolumn {Number} AlignmentTemplate {}', sub { $tokens->unlist, ($column ? afterCellUnlist($$column{after}) : ())); }); +DefMacro('\lx@alignment@multicolumn {Number}{}{}', sub { + my ($gullet, $span, $template, $tokens) = @_; + $span = $span->valueOf; + # First part, like \multispan + (T_CS('\omit'), (map { (T_CS('\span'), T_CS('\omit')) } 1 .. $span - 1), + T_CS('\lx@alignment@altcolumn'), T_BEGIN, $template, T_END, $tokens); }); +DefMacro('\lx@alignment@altcolumn AlignmentTemplate', sub { + my ($gullet, $template) = @_; + if (my $alignment = LookupValue('Alignment')) { + $alignment->replaceColumn($template->column(1)); } }); + DefConditionalI('\if@in@lx@alignment', undef, sub { LookupValue('Alignment'); }); DefPrimitive('\lx@alignment@bindings AlignmentTemplate []', sub { diff --git a/lib/LaTeXML/Package/array.sty.ltxml b/lib/LaTeXML/Package/array.sty.ltxml index 47dec92c1..e95d6e9f9 100644 --- a/lib/LaTeXML/Package/array.sty.ltxml +++ b/lib/LaTeXML/Package/array.sty.ltxml @@ -24,18 +24,24 @@ DefMacroI('\lasthline', undef, '\hline'); DefColumnType('>{}', sub { $LaTeXML::BUILD_TEMPLATE->addBeforeColumn($_[1]->unlist); return; }); DefColumnType('<{}', sub { $LaTeXML::BUILD_TEMPLATE->addAfterColumn($_[1]->unlist); return; }); -# This is the same as p +# This is the same as p, but using \vtop # but really needs to specify vertical alignment as centered DefColumnType('m{Dimension}', sub { - $LaTeXML::BUILD_TEMPLATE->addColumn(before => Tokens(T_CS('\vtop'), T_BEGIN), #Tokens(T_BEGIN), - after => Tokens(T_END), - align => 'justify', width => $_[1], vattach => 'middle'); return; }); -# This is also the same as p + $LaTeXML::BUILD_TEMPLATE->addColumn( + before => Tokens(T_CS('\vbox'), T_BEGIN, T_BEGIN, + T_CS('\hsize'), $_[1]->revert, T_CS('\relax')), + after => Tokens(T_END, T_END), + align => 'justify', width => $_[1], + vattach => 'middle'); return; }); +# This is also the same as p, but SHOULD use \vbot (if there was one) # but really needs to specify vertical alignment as bottom DefColumnType('b{Dimension}', sub { - $LaTeXML::BUILD_TEMPLATE->addColumn(before => Tokens(T_CS('\vbox'), T_BEGIN), #Tokens(T_BEGIN), - after => Tokens(T_END), - align => 'justify', width => $_[1], vattach => 'bottom'); return; }); + $LaTeXML::BUILD_TEMPLATE->addColumn( + before => Tokens(T_CS('\vbox'), T_BEGIN, T_BEGIN, + T_CS('\hsize'), $_[1]->revert, T_CS('\relax')), + after => Tokens(T_END, T_END), + align => 'justify', width => $_[1], + vattach => 'bottom'); return; }); # Like @{}, but should NOT suppress intercolumn space DefColumnType('!{}', sub { From 4e6ba3f8604181ddf79c55701d016f75f1ab973d Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Wed, 13 May 2026 16:50:31 -0400 Subject: [PATCH 18/31] Redo p{},m{},b{} to create ltx:inline-block, so they work alongside @{}, and also process the contents with \hsize set for proper sizing --- lib/LaTeXML/Engine/TeX_Tables.pool.ltxml | 14 ++++++++++---- lib/LaTeXML/Package/array.sty.ltxml | 23 +++++++++-------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/lib/LaTeXML/Engine/TeX_Tables.pool.ltxml b/lib/LaTeXML/Engine/TeX_Tables.pool.ltxml index 9197571f1..ab924fe4b 100644 --- a/lib/LaTeXML/Engine/TeX_Tables.pool.ltxml +++ b/lib/LaTeXML/Engine/TeX_Tables.pool.ltxml @@ -64,14 +64,20 @@ DefColumnType('c', sub { DefColumnType('r', sub { $LaTeXML::BUILD_TEMPLATE->addColumn(before => Tokens(T_CS('\hfil'))); return; }); +# Note that p typesets as a paragraph of given width, +# BUT @{} might add other contents to the cell! So, we need an inline-block DefColumnType('p{Dimension}', sub { $LaTeXML::BUILD_TEMPLATE->addColumn( - before => Tokens(T_CS('\vtop'), T_BEGIN, T_BEGIN, - T_CS('\hsize'), $_[1]->revert, T_CS('\relax')), - after => Tokens(T_END, T_END), + before => Tokens(T_CS('\lx@tabular@p'), T_LETTER('t'), T_BEGIN, $_[1]->revert, T_END, T_BEGIN), + after => Tokens(T_END), vattach => 'top', - align => 'justify', width => $_[1], ); return; }); +# Make sure the content sees the desired width as \hsize +DefMacro('\lx@tabular@p{}{}', '\hsize=#2\relax\lx@tabular@p@{#1}{#2}'); +DefConstructor('\lx@tabular@p@{}{Dimension} VBoxContents', + "?#3(#3)()", + sizer => '#3', reversion => '#3', + properties => sub { (vattach => translateAttachment($_[1])); }); DefColumnType('*{Number}{}', sub { my ($gullet, $n, $pattern) = @_; diff --git a/lib/LaTeXML/Package/array.sty.ltxml b/lib/LaTeXML/Package/array.sty.ltxml index e95d6e9f9..ec26b51be 100644 --- a/lib/LaTeXML/Package/array.sty.ltxml +++ b/lib/LaTeXML/Package/array.sty.ltxml @@ -24,24 +24,19 @@ DefMacroI('\lasthline', undef, '\hline'); DefColumnType('>{}', sub { $LaTeXML::BUILD_TEMPLATE->addBeforeColumn($_[1]->unlist); return; }); DefColumnType('<{}', sub { $LaTeXML::BUILD_TEMPLATE->addAfterColumn($_[1]->unlist); return; }); -# This is the same as p, but using \vtop -# but really needs to specify vertical alignment as centered +# m{} and b{} are like p{}, but vertical alignment is middle or bottom DefColumnType('m{Dimension}', sub { $LaTeXML::BUILD_TEMPLATE->addColumn( - before => Tokens(T_CS('\vbox'), T_BEGIN, T_BEGIN, - T_CS('\hsize'), $_[1]->revert, T_CS('\relax')), - after => Tokens(T_END, T_END), - align => 'justify', width => $_[1], - vattach => 'middle'); return; }); -# This is also the same as p, but SHOULD use \vbot (if there was one) -# but really needs to specify vertical alignment as bottom + before => Tokens(T_CS('\lx@tabular@p'), T_LETTER('m'), T_BEGIN, $_[1]->revert, T_END, T_BEGIN), + after => Tokens(T_END), + vattach => 'middle', + ); return; }); DefColumnType('b{Dimension}', sub { $LaTeXML::BUILD_TEMPLATE->addColumn( - before => Tokens(T_CS('\vbox'), T_BEGIN, T_BEGIN, - T_CS('\hsize'), $_[1]->revert, T_CS('\relax')), - after => Tokens(T_END, T_END), - align => 'justify', width => $_[1], - vattach => 'bottom'); return; }); + before => Tokens(T_CS('\lx@tabular@p'), T_LETTER('b'), T_BEGIN, $_[1]->revert, T_END, T_BEGIN), + after => Tokens(T_END), + vattach => 'bottom', + ); return; }); # Like @{}, but should NOT suppress intercolumn space DefColumnType('!{}', sub { From dda21baaf3949bb804bb9bc0aa7164e4fa253618 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Wed, 13 May 2026 16:51:16 -0400 Subject: [PATCH 19/31] Update several test cases affected by the different tabular p{} processing --- t/alignment/array.xml | 16 +++--- t/alignment/cells.xml | 24 ++++---- t/alignment/colortbls.xml | 72 ++++++++++++------------ t/alignment/halignatt.xml | 2 +- t/alignment/tabular.xml | 40 +++++++------- t/babel/numprints.xml | 10 ++-- t/graphics/graphrot.xml | 100 +++++++++++++++++++++------------- t/math/array_newline_math.xml | 2 +- 8 files changed, 146 insertions(+), 120 deletions(-) diff --git a/t/alignment/array.xml b/t/alignment/array.xml index be6d249e3..4096fbc79 100644 --- a/t/alignment/array.xml +++ b/t/alignment/array.xml @@ -19,24 +19,24 @@ - - Lorem ipsum dolor sit amet, consectetur adipisicing elit + + Lorem ipsum dolor sit amet, consectetur adipisicing elit - + Lorem ipsum dolor sit amet, consectetur adipisicing elit - + Lorem ipsum dolor sit amet, consectetur adipisicing elit - - Lorem ipsum dolor sit amet, consectetur adipisicing elit + + Lorem ipsum dolor sit amet, consectetur adipisicing elit - + Lorem ipsum dolor sit amet, consectetur adipisicing elit - + Lorem ipsum dolor sit amet, consectetur adipisicing elit diff --git a/t/alignment/cells.xml b/t/alignment/cells.xml index 54cb7453f..82b74577a 100644 --- a/t/alignment/cells.xml +++ b/t/alignment/cells.xml @@ -69,8 +69,8 @@ - - Cell long text with predefined width + + Cell long text with predefined width @@ -79,8 +79,8 @@ - - Cell long… + + Cell long… @@ -148,13 +148,13 @@ - - Second multilined + + Second multilined - - column head + + column head @@ -298,8 +298,8 @@ - - Cell … + + Cell … @@ -308,8 +308,8 @@ - - Cell … + + Cell … diff --git a/t/alignment/colortbls.xml b/t/alignment/colortbls.xml index 0ab5925dc..9a086e57b 100644 --- a/t/alignment/colortbls.xml +++ b/t/alignment/colortbls.xml @@ -95,11 +95,11 @@ p{3cm}}"?> - - P-column + + P-column - - and another one + + and another one @@ -108,8 +108,8 @@ p{3cm}}"?> - Total - (wrong) + Total + (wrong) 100⋅6 @@ -117,11 +117,11 @@ p{3cm}}"?> - - Some long text in the first column + + Some long text in the first column - - bbb + + bbb @@ -130,11 +130,11 @@ p{3cm}}"?> - - aaa + + aaa - - and some long text in the second column + + and some long text in the second column @@ -143,8 +143,8 @@ p{3cm}}"?> - Total - (wrong) + Total + (wrong) 100⋅6 @@ -152,11 +152,11 @@ p{3cm}}"?> - - aaa + + aaa - - bbb + + bbb @@ -165,12 +165,12 @@ p{3cm}}"?> - - Note that the coloured rules in all columns stretch to accomodate + + Note that the coloured rules in all columns stretch to accomodate large entries in one column. - - bbb + + bbb @@ -179,11 +179,11 @@ large entries in one column. - - aaa + + aaa - - bbb + + bbb @@ -192,11 +192,11 @@ large entries in one column. - - aaa + + aaa - - Depending on your driver you may get unsightly gaps or lines + + Depending on your driver you may get unsightly gaps or lines where the ‘screens’ used to produce different shapes interact badly. You may want to cause adjacent panels of the same colour by specifying a larger overhang @@ -209,11 +209,11 @@ or by adding some negative space (in a ”“noalign” between rows. - - aaa + + aaa - - bbb + + bbb diff --git a/t/alignment/halignatt.xml b/t/alignment/halignatt.xml index 41296325b..5e81f40cd 100644 --- a/t/alignment/halignatt.xml +++ b/t/alignment/halignatt.xml @@ -53,7 +53,7 @@ .95* - * (first quarter only) + * (first quarter only) diff --git a/t/alignment/tabular.xml b/t/alignment/tabular.xml index b7cd9df21..5bcfd6edf 100644 --- a/t/alignment/tabular.xml +++ b/t/alignment/tabular.xml @@ -13,13 +13,13 @@ Price - + Year low high - Comments + Comments @@ -27,24 +27,24 @@ 1971 97– 245 - - Bad year. + + Bad year. 72 245– 245 - - Light trading due to a heavy winter. + + Light trading due to a heavy winter. 73 245– 2001 - - No gnus was very good gnus this year. + + No gnus was very good gnus this year. @@ -77,25 +77,25 @@ - 1 - a + 1 + a 2 - - b + + b 3 - - c + + c 4 - 1 - a a a a a a a a a a + 1 + a a a a a a a a a a 2 - - b + + b 3 - - c + + c 4 diff --git a/t/babel/numprints.xml b/t/babel/numprints.xml index 2062b1fee..bb89e864a 100644 --- a/t/babel/numprints.xml +++ b/t/babel/numprints.xml @@ -811,15 +811,15 @@ vs. - without braces + without braces with braces with braces and box - with braces, exp, and box + with braces, exp, and box - error + error abc def @@ -901,7 +901,7 @@ vs. rt - mor + mor e45,1 @@ -963,7 +963,7 @@ vs. txt - notblu + notblu e45,1 diff --git a/t/graphics/graphrot.xml b/t/graphics/graphrot.xml index 9cb6f16ea..6ae8076ff 100644 --- a/t/graphics/graphrot.xml +++ b/t/graphics/graphrot.xml @@ -395,7 +395,7 @@ the whales Save the whale Save the + Table 5 Table 5 @@ -413,12 +413,12 @@ the whales Save the whale Save the Pottery Flint Animal - - Stone + + Stone Other - - C14 Dates + + C14 Dates @@ -430,15 +430,28 @@ the whales Save the whale Save the Bones - + - + - Grooved Ware - + + + + + + + + + + + + + + Grooved Ware + 784 @@ -457,14 +470,14 @@ the whales Save the whale Save the × 8 - + × 2 bone - - 2150 + + 2150 ± @@ -488,12 +501,12 @@ the whales Save the whale Save the × 21 - - Hammerstone + + Hammerstone — - - — + + — @@ -513,12 +526,12 @@ the whales Save the whale Save the × 57* - - — + + — — - - 1990 + + 1990 ± @@ -546,17 +559,30 @@ the whales Save the whale Save the × 8 - - — + + — Fired clay - - — + + — + + + + + + + + + + + + + Beaker - + 552 @@ -567,12 +593,12 @@ the whales Save the whale Save the P7–14 — — - - — + + — — - - — + + — @@ -588,12 +614,12 @@ the whales Save the whale Save the — - - Quartzite-lump + + Quartzite-lump — - - — + + — @@ -609,12 +635,12 @@ the whales Save the whale Save the — - - — + + — — - - — + + — diff --git a/t/math/array_newline_math.xml b/t/math/array_newline_math.xml index 71cc48525..6eb31fd14 100644 --- a/t/math/array_newline_math.xml +++ b/t/math/array_newline_math.xml @@ -13,7 +13,7 @@ 4 - + From 004f732cc1cd48992ecdecbfb7dc42e7aa0417f3 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Tue, 26 May 2026 12:20:41 -0400 Subject: [PATCH 20/31] Support passing more size releated properties to Font: pad(top|bottom|right|left); tweak debug feedback more informative, less noisy --- lib/LaTeXML/Core/Box.pm | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/LaTeXML/Core/Box.pm b/lib/LaTeXML/Core/Box.pm index 07216bfe6..87460186e 100644 --- a/lib/LaTeXML/Core/Box.pm +++ b/lib/LaTeXML/Core/Box.pm @@ -38,7 +38,7 @@ sub Box { $properties{height} = Dimension(0) unless defined $properties{height}; $properties{depth} = Dimension(0) unless defined $properties{depth}; } my $state = $STATE; - my $mode = $properties{mode} || $state->lookupValue('MODE') || 'restricted_horizontal'; + my $mode = $properties{mode} || $state->lookupValue('MODE') || 'restricted_horizontal'; if ($state->lookupValue('IN_MATH')) { my $attr = (defined $string) && $state->lookupValue('math_token_attributes_' . $string); my $usestring = ($attr && $$attr{replace}) || $string; @@ -47,7 +47,7 @@ sub Box { mode => $mode, ($attr ? %$attr : ()), %properties); } else { return LaTeXML::Core::Box->new($string, $font, $locator, $tokens, - mode => $mode, %properties); } } + mode => $mode, %properties); } } #====================================================================== # Box Object @@ -113,10 +113,10 @@ my %mode_abbrev = ( sub _stringify { my ($self) = @_; - my $type = ref $self; - my $mode = $$self{properties}{mode}; + my $type = ref $self; + my $mode = $$self{properties}{mode}; $type =~ s/^LaTeXML::Core:://; - $type .= '!'. ($mode_abbrev{$mode} || $mode) if $mode; + $type .= '!' . ($mode_abbrev{$mode} || $mode) if $mode; return $type; } sub stringify { @@ -235,12 +235,6 @@ sub getSize { unless (defined $$props{cwidth}) && (defined $$props{cheight}) && (defined $$props{cdepth}); - Debug("SIZE of $self" - . "\n preassigned: " . _showsize($$props{width}, $$props{height}, $$props{depth}) - . "\n calculated : " . _showsize($$props{cwidth}, $$props{cheight}, $$props{cdepth}) - . "\n w/options " . join(',', map { $_ . "=" . ToString($options{$_}); } sort keys %options) - . "\n =>: " . _showsize($$props{width} || $$props{cwidth}, $$props{height} || $$props{cheight}, $$props{depth} || $$props{cdepth}) - . "\n Of " . ToString($self)) if $LaTeXML::DEBUG{size}; return ($$props{width} || $$props{cwidth}, $$props{height} || $$props{cheight}, $$props{depth} || $$props{cdepth}, @@ -260,19 +254,25 @@ sub showSize { #omg # Fake computing the dimensions of strings (typically single chars). # Eventually, this needs to link into real font data +our @sizing_properties = (qw( + width height depth totalheight vattach layout + padtop padbottom padleft padright)); + sub computeSizeStore { my ($self, %options) = @_; no warnings 'recursion'; my $props = $self->getPropertiesRef; - $options{width} = $$props{width} if $$props{width}; - $options{height} = $$props{height} if $$props{height}; - $options{depth} = $$props{depth} if $$props{depth}; - $options{vattach} = $$props{vattach} if $$props{vattach}; - $options{layout} = $$props{layout} if $$props{layout}; + map { $options{$_} = $$props{$_} if defined $$props{$_}; } @sizing_properties; my ($w, $h, $d) = $self->computeSize(%options); $$props{cwidth} = $w unless defined $$props{cwidth}; $$props{cheight} = $h unless defined $$props{cheight}; $$props{cdepth} = $d unless defined $$props{cdepth}; + Debug("SIZE of $self" + . "\n preassigned: " . _showsize($$props{width}, $$props{height}, $$props{depth}) + . "\n calculated : " . _showsize($$props{cwidth}, $$props{cheight}, $$props{cdepth}) + . "\n w/options " . join(',', map { $_ . "=" . ToString($options{$_}); } sort keys %options) + . "\n =>: " . _showsize($$props{width} || $$props{cwidth}, $$props{height} || $$props{cheight}, $$props{depth} || $$props{cdepth}) + . "\n Of " . ToString($self)) if $LaTeXML::DEBUG{size}; return; } sub computeSize { From 2b2ed28987f5fd68acf34451562fef1860da1802 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Tue, 26 May 2026 12:24:22 -0400 Subject: [PATCH 21/31] Simplify simple underline/overline --- lib/LaTeXML/Engine/TeX_Math.pool.ltxml | 28 +++++++++----------------- lib/LaTeXML/resources/CSS/LaTeXML.css | 3 +++ t/fonts/marvosym.xml | 4 ++-- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/lib/LaTeXML/Engine/TeX_Math.pool.ltxml b/lib/LaTeXML/Engine/TeX_Math.pool.ltxml index 2f81ba7a4..b8bc3b680 100644 --- a/lib/LaTeXML/Engine/TeX_Math.pool.ltxml +++ b/lib/LaTeXML/Engine/TeX_Math.pool.ltxml @@ -300,7 +300,7 @@ sub cleanup_XMText { $document->replaceNode($m, map { $_->childNodes } $m->childNodes); } else { # Otherwise, wrap whatever it is in an XMText $document->wrapNodes('ltx:XMText', $m); } - } } } + } } } # And now we don't need the XMText any more. foreach my $attr ($textnode->attributes) { # Copy the child's attributes (should Merge!!) $table->setAttribute($attr->nodeName => $attr->getValue); } @@ -423,7 +423,7 @@ sub scriptHandler { my $c = (($op eq 'SUPERSCRIPT') ? '^' : '_'); Error('unexpected', $c, $stomach, "Script $c can only appear in math mode"); return Box($c, undef, undef, (($op eq 'SUPERSCRIPT') ? T_SUPER : T_SUB)); -} } + } } DefPrimitiveI(T_SUPER, undef, sub { scriptHandler($_[0], 'SUPERSCRIPT'); }); DefPrimitiveI(T_SUB, undef, sub { scriptHandler($_[0], 'SUBSCRIPT'); }); @@ -544,7 +544,7 @@ DefRewrite(xpath => 'descendant::ltx:Math[child::ltx:XMath[child::ltx:XMApp[' . elsif ($role eq 'FLOATSUBSCRIPT') { $document->insertElement('ltx:sub', $text); return; } - } } } } + } } } } # should never happen, but just in case: Info("rewrite", "footnotemark", "Failed to find floating node in: " . $math->toString(1)); $document->getNode->appendChild($math); @@ -819,7 +819,7 @@ sub augmentDelimiterProperties { if (exists $$entry{char}) { # replace the char content! if (my $char = $$entry{char}) { $delim->firstChild->setData($char); } } - } } + } } return; } # Useful for afterConstruct of delimiter sizing macros (eg. \bigl) @@ -952,24 +952,16 @@ DefMath('\lx@math@overline{}', UTF(0xAF), operator_role => 'OVERACCENT', operator_stretchy => 'true', name => 'overline', alias => '\overline'); DefConstructor('\lx@text@overline{}', - "#1", - enterHorizontal => 1, mode => 'restricted_horizontal', - sizer => sub { - my $text = $_[0]->getArg(1); - my ($w, $h, $d) = $text->getSize; - my $pad = Dimension($text->getFont->getEXHeight); # WAG - return ($w, $h->add($pad), $d); }); + "#1", + enterHorizontal => 1, mode => 'restricted_horizontal', + sizer => '#1', properties => { padtop => Dimension('2pt') }); DefMath('\lx@math@underline{}', UTF(0xAF), operator_role => 'UNDERACCENT', operator_stretchy => 'true', name => 'underline', alias => '\underline'); DefConstructor('\lx@text@underline{}', - "#1", - enterHorizontal => 1, mode => 'restricted_horizontal', - sizer => sub { - my $text = $_[0]->getArg(1); - my ($w, $h, $d) = $text->getSize; - my $pad = Dimension($text->getFont->getEXHeight); # WAG - return ($w, $h, $d->add($pad)); }); + "#1", + enterHorizontal => 1, mode => 'restricted_horizontal', + sizer => '#1', properties => { padbottom => Dimension('2pt') }); DefMath('\lx@math@overrightarrow{}', "\x{2192}", operator_role => 'OVERACCENT', operator_stretchy => 'true', name => 'overrightarrow', alias => '\overrightarrow'); diff --git a/lib/LaTeXML/resources/CSS/LaTeXML.css b/lib/LaTeXML/resources/CSS/LaTeXML.css index 4f4578337..758ff3d0c 100644 --- a/lib/LaTeXML/resources/CSS/LaTeXML.css +++ b/lib/LaTeXML/resources/CSS/LaTeXML.css @@ -442,6 +442,9 @@ span.ltx_framed { display:inline-block; text-indent:0; } /* avoid padding/ .ltx_rule { vertical-align: bottom; height: 0.4pt; width: 0.4pt; } +.ltx_underline { text-decoration: underline; } +.ltx_overline { text-decoration: overline; } + /*====================================================================== Misc */ /* .ltx_verbatim*/ diff --git a/t/fonts/marvosym.xml b/t/fonts/marvosym.xml index d902bc3ac..b0f38ec92 100644 --- a/t/fonts/marvosym.xml +++ b/t/fonts/marvosym.xml @@ -126,9 +126,9 @@ △╳, Ⓐ, Ⓟ, -Ⓟ, +Ⓟ, Ⓕ, -Ⓕ, +Ⓕ, \Ironing, \ironing, \IRONING, From ab6fe78bb6f4bb46cfa4c584aa548f5fbc4c84df Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Tue, 26 May 2026 12:25:24 -0400 Subject: [PATCH 22/31] Update deprecated --- lib/LaTeXML/Package/fancyvrb.sty.ltxml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/LaTeXML/Package/fancyvrb.sty.ltxml b/lib/LaTeXML/Package/fancyvrb.sty.ltxml index 69e3afdac..caee3477f 100644 --- a/lib/LaTeXML/Package/fancyvrb.sty.ltxml +++ b/lib/LaTeXML/Package/fancyvrb.sty.ltxml @@ -22,7 +22,7 @@ InputDefinitions('fancyvrb', type => 'sty', noltxml => 1); # Hack internals to add css class ltx_nowrap Let('\lx@save@FancyVerbFormatLine', '\FancyVerbFormatLine'); DefMacro('\FancyVerbFormatLine{}', - '\@ADDCLASS{ltx_verbatim}\lx@save@FancyVerbFormatLine{#1}'); + '\lx@add@cssclass{ltx_verbatim}\lx@save@FancyVerbFormatLine{#1}'); #********************************************************************** 1; From 55458d0beb6517ccc858bfbc180fe91a0b4f7314 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Tue, 26 May 2026 12:27:24 -0400 Subject: [PATCH 23/31] Add padding to items, equations --- lib/LaTeXML/Engine/TeX_Math.pool.ltxml | 5 +++- .../Engine/latex_constructs.pool.ltxml | 27 ++++++++++++------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/lib/LaTeXML/Engine/TeX_Math.pool.ltxml b/lib/LaTeXML/Engine/TeX_Math.pool.ltxml index b8bc3b680..fe64321c2 100644 --- a/lib/LaTeXML/Engine/TeX_Math.pool.ltxml +++ b/lib/LaTeXML/Engine/TeX_Math.pool.ltxml @@ -133,7 +133,10 @@ DefConstructorI('\lx@begin@display@math', undef, beforeDigest => sub { $_[0]->enterHorizontal; $_[0]->beginMode('display_math'); }, - properties => { mode => 'display_math' }, + properties => sub { + (mode => 'display_math', + padtop => LookupRegister('\abovedisplayskip'), + padbottom => LookupRegister('\belowdisplayskip')); }, captureBody => 1); DefConstructorI('\lx@end@display@math', undef, "", reversion => Tokens(T_MATH, T_MATH), diff --git a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml index 3f57de830..d9b8df62a 100644 --- a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml +++ b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml @@ -1368,7 +1368,11 @@ sub RefStepItemCounter { my %attr = (); my $sep = LookupDimension('\itemsep'); if (($n > 0) && $sep && ($sep->valueOf != LookupDimension('\lx@default@itemsep')->valueOf)) { - $attr{itemsep} = $sep; } + $attr{itemsep} = $sep; + # this SHOULD pad the item, but \item is ONLY the marker, not the item content! +## $attr{padtop} = $sep; +## $attr{height} = Dimension('1em'); + } if (defined $tag) { my @props = RefStepID($counter); if (IsEmpty($tag)) { # empty tag? @@ -1670,11 +1674,11 @@ DefConstructor('\trivlist@item@ OptionalUndigested', properties => sub { ($_[1] ? (tag => Digest(Expand($_[1]))) : ()); }); DefMacro('\@trivlist', '\relax', locked => 1); -DefRegister('\topsep' => Glue(0)); -DefRegister('\partopsep' => Glue(0)); -DefRegister('\lx@default@itemsep' => Glue(0)); -DefRegister('\itemsep' => Glue(0)); -DefRegister('\parsep' => Glue(0)); +DefRegister('\topsep' => Glue('8pt plus 2pt minus 4pt')); +DefRegister('\partopsep' => Glue('2pt plus 1pt minus 1pt')); +DefRegister('\lx@default@itemsep' => Glue('4pt plus 2pt minus 1pt')); +DefRegister('\itemsep' => Glue('4pt plus 2pt minus 1pt')); +DefRegister('\parsep' => Glue('4pt plus 2pt minus 1pt')); DefRegister('\@topsep' => Glue(0)); DefRegister('\@topsepadd' => Glue(0)); DefRegister('\@outerparskip' => Glue(0)); @@ -2024,6 +2028,9 @@ sub afterEquation { elsif ($whatsit) { $whatsit->setProperties(%{ LookupValue('EQUATIONROW_TAGS') }); } $$numbering{in_equation} = 0; + $whatsit->setProperties( + padtop => LookupRegister('\abovedisplayskip'), + padbottom => LookupRegister('\belowdisplayskip')) if $whatsit; return; } # My first inclination is to Lock {math}, but it is surprisingly common to redefine it in silly ways... So...? @@ -2969,7 +2976,7 @@ sub defineNewTheorem { T_CS('\let'), T_CS('\the' . $counter), T_CS('\@empty'), Invocation(T_CS('\lx@make@tags'), T_OTHER($thmset)), T_END); - } } + } } else { %ctr = RefStepCounter($thmset); } } my $title = DigestText(Tokens(T_BEGIN, @@ -3317,7 +3324,7 @@ sub arrange_panels_and_breaks { my $block = $document->wrapNodes('ltx:block', $prev_node, $child); push(@row_contents, [$block, 'ltx:block', $prev_width + $child_width]); pop(@all_panels); push(@all_panels, $block); - } } + } } else { # otherwise keep the last panel as-is, and append a sibling push(@row_contents, $prev_panel); @@ -4762,8 +4769,8 @@ DefConstructor('\lx@parbox[][Dimension] OptionalUndigested {Dimension} VBoxConte sizer => '#5', properties => sub { (width => $_[4], - vattach => translateAttachment($_[1]), - height => $_[2]); }, + vattach => translateAttachment($_[1]), + totalheight => $_[2]); }, mode => 'inline_internal_vertical', robust => 1, beforeDigest => sub { From e654a134d03a00609793fc05cef73560b4be6c7b Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Tue, 26 May 2026 12:29:06 -0400 Subject: [PATCH 24/31] Let space squeeze when laying out paragraphs; more informative debugging; support properties for padding, totalheight --- lib/LaTeXML/Common/Font.pm | 58 ++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/lib/LaTeXML/Common/Font.pm b/lib/LaTeXML/Common/Font.pm index ea1855d6e..d9a3c7ba2 100644 --- a/lib/LaTeXML/Common/Font.pm +++ b/lib/LaTeXML/Common/Font.pm @@ -646,6 +646,7 @@ sub computeBoxesSize { || $STATE->lookupDefinition(T_CS('\hsize')); $wrapwidth = $wrapwidth->valueOf if ref $wrapwidth; # Register or Dimension $wrapwidth = $wrapwidth->valueOf if ref $wrapwidth; } # still Dimension (Register) + my $maxwidth = $wrapwidth || 0; no warnings 'recursion'; my @boxes = grep { !(ref $_) || !$_->getProperty('isEmpty') } grep { !(ref $_) || $_->can('getSize'); } $boxes->unlist; @@ -659,6 +660,7 @@ sub computeBoxesSize { && (($box->getProperty('mode') || '') eq 'horizontal')) { my $width = $box->getProperty('width') || $wrapwidth; $width = $width->valueOf if ref $width; + $maxwidth = $width if $width && $width > $maxwidth; push(@lines, $self->computeBoxesSize_lines($width, $self->computeBoxesSize_words($box->unlist))); } else { @@ -666,17 +668,30 @@ sub computeBoxesSize { push(@lines, [$w, $h, $d]) if $w || $h || $d; } } } else { # Scan all boxes, collecting into "words", then (possibly) break into lines. + # Should get single line, if no $wrapwidth my @words = $self->computeBoxesSize_words(@boxes); @lines = $self->computeBoxesSize_lines($wrapwidth, @words); } # ---------------------------------------------------------------------- # Now, stack up the multiple lines my ($wd, $ht, $dp) = $self->computeBoxesSize_stack($vattach, @lines); - + $wd = $maxwidth if $wd && $maxwidth; # Set to wrapwidth, unless empty. + if (my $th = $options{totalheight}) { # divie up totalheight, if requested + $th = $th->valueOf; + my $diff = $th - $ht - $dp; + if ($diff > 0) { + if ($vattach eq 'bottom') { $ht += $diff; } + elsif ($vattach eq 'middle') { $ht += $diff/2; $dp += $diff/2; } + else { $dp += $diff; } } } Debug("Size boxes " . join(',', map { $_ . '=' . ToString($options{$_}); } sort keys %options) . "\n" . " Boxes: " . ToString($boxes) . "\n" . " Boxes: " . Stringify($boxes) . "\n" + . " Options:" . join(',',map { $_."=".ToString($options{$_}); } sort keys %options) . " Sizes: " . join("\n", map { _showsize(@$_); } @lines) . "\n" . " => " . _showsize($wd, $ht, $dp)) if $LaTeXML::DEBUG{'size-detailed'}; + $wd += $options{padleft}->valueOf if $options{padleft}; + $wd += $options{padright}->valueOf if $options{padright}; + $ht += $options{padtop}->valueOf if $options{padtop}; + $dp += $options{padbottom}->valueOf if $options{padbottom}; return (Dimension($wd), Dimension($ht), Dimension($dp)); } # Compute (w/guards) the size of a single box @@ -706,6 +721,7 @@ sub computeBoxesSize_words { no warnings 'recursion'; my ($self, @boxes) = @_; my @words = (); + my @word = (); my $prevbox; my $prevspace = 0; my $size = int($self->getSize || DEFSIZE() || 10); @@ -715,21 +731,22 @@ sub computeBoxesSize_words { # Check for possible line-break points if ((ref $box) && $box->getProperty('isBreak')) { if ($wd || $ht || $dp || ($prevspace > 0)) { - push(@words, [$prevspace, $wd, $ht, $dp]); - $wd = $ht = $dp = 0; $prevspace = -1; } + push(@words, [$prevspace, $wd, $ht, $dp, @word]); + $wd = $ht = $dp = 0; $prevspace = -1; @word = (); } else { $prevspace = -1; } } # Pernaps not "isSpace", but excluding struts, neg space, etc ??? elsif ((ref $box) && $box->getProperty('isSpace') && !$box->getProperty('isVerticalSpace')) { if ($wd || $ht || $dp || ($prevspace < 0)) { - push(@words, [$prevspace, $wd, $ht, $dp]); - $wd = $ht = $dp = 0; $prevspace = $w; } + push(@words, [$prevspace, $wd, $ht, $dp, @word]); + $wd = $ht = $dp = 0; $prevspace = $w; @word = (); } else { $prevspace += $w; } } else { # Else accumulate into "word" $wd += $w; $ht = max($ht, $h); $dp = max($dp, $d); + push(@word, $box); # Kern HACK for lists of individual Box's if ($prevbox && (ref $prevbox eq 'LaTeXML::Core::Box') && (ref $box eq 'LaTeXML::Core::Box')) { my $prevchar = substr($prevbox->getString || '', -1, 1); @@ -741,28 +758,32 @@ sub computeBoxesSize_words { $wd += $size * $kern; } } } $prevbox = $box; } - if ($wd || $ht || $dp || $prevspace) { # be sure to get last bit - push(@words, [$prevspace, $wd, $ht, $dp]); } + if ($wd || $ht || $dp || $prevspace || @word) { # be sure to get last bit + push(@words, [$prevspace, $wd, $ht, $dp, @word]); } return @words; } # do line breaking of words into lines, according to $wrapwidth (if), or explicit breaks. sub computeBoxesSize_lines { my ($self, $wrapwidth, @words) = @_; my @lines = (); + my @line = (); + my $fuzz = Dimension('1pt')->valueOf; + my $squeeze = ($wrapwidth ? 0.6 : 1.0); # Let spaces shrink in paragraph mode my ($wd, $ht, $dp) = (0, 0, 0); foreach my $item (@words) { - my ($space, $w, $h, $d) = @$item; + my ($space, $w, $h, $d, @word) = @$item; if ($space == -1) { - push(@lines, [$wd, $ht, $dp]) if $wd; - $wd = $w; $ht = $h; $dp = $d; } - elsif ((defined $wrapwidth) && ($wd + $space * 0.5 + $w > $wrapwidth)) { - push(@lines, [$wrapwidth || $wd, $ht, $dp]) if $wd; - $wd = $w; $ht = $h; $dp = $d; } + push(@lines, [$wd, $ht, $dp, @line]) if $wd; + $wd = $w; $ht = $h; $dp = $d; @line = @word; } + elsif ((defined $wrapwidth) && ($wd + $space * 0.5 + $w > $wrapwidth + $fuzz)) { + push(@lines, [$wd, $ht, $dp, @line]) if $wd; + $wd = $w; $ht = $h; $dp = $d; @line = @word; } else { - $wd += $space + $w; + $wd += $space*$squeeze + $w; $ht = max($ht, $h); - $dp = max($dp, $d); } } - push(@lines, [$wrapwidth || $wd, $ht, $dp]) if $wd || $ht || $dp; + $dp = max($dp, $d); + push(@line, @word); } } + push(@lines, [$wd, $ht, $dp, @line]) if $wd || $ht || $dp; return @lines; } # Sum up a stack of lines, determining w as max, and h & d according to $vattach. @@ -835,8 +856,9 @@ sub math_bearing { return $STATE->lookupDefinition($$mathbearingreg[abs($bearing)])->valueOf->spValue; } sub _showsize { - my ($wd, $ht, $dp) = @_; - return ($wd / $UNITY) . " x " . ($ht / $UNITY) . " + " . ($dp / $UNITY); } + my ($wd, $ht, $dp, @stuff) = @_; + return ($wd / $UNITY) . " x " . ($ht / $UNITY) . " + " . ($dp / $UNITY) + . (@stuff ? ' '.join('', map { ToString($_); } @stuff) : ''); } sub isSticky { my ($self) = @_; From f6309c6f8db37633472cd322967350899cfc2d88 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 28 May 2026 17:22:49 -0400 Subject: [PATCH 25/31] Bypass size computation if Box size completely specified --- lib/LaTeXML/Core/Box.pm | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/LaTeXML/Core/Box.pm b/lib/LaTeXML/Core/Box.pm index 87460186e..87087c629 100644 --- a/lib/LaTeXML/Core/Box.pm +++ b/lib/LaTeXML/Core/Box.pm @@ -263,7 +263,15 @@ sub computeSizeStore { no warnings 'recursion'; my $props = $self->getPropertiesRef; map { $options{$_} = $$props{$_} if defined $$props{$_}; } @sizing_properties; - my ($w, $h, $d) = $self->computeSize(%options); + my $w = $options{width}; + my $h = $options{height}; + my $d = $options{depth}; + if ((defined $w) && (defined $h) && (defined $d)) { + $w = Dimension($w) unless ref $w; + $h = Dimension($h) unless ref $h; + $d = Dimension($d) unless ref $d; } + else { + ($w, $h, $d) = $self->computeSize(%options); } $$props{cwidth} = $w unless defined $$props{cwidth}; $$props{cheight} = $h unless defined $$props{cheight}; $$props{cdepth} = $d unless defined $$props{cdepth}; From c56d7ec189b619ac3423a06a75b49acc3392f02c Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 28 May 2026 17:28:02 -0400 Subject: [PATCH 26/31] Make \vskip specified as pure height, no width, depth --- lib/LaTeXML/Engine/TeX_Glue.pool.ltxml | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/LaTeXML/Engine/TeX_Glue.pool.ltxml b/lib/LaTeXML/Engine/TeX_Glue.pool.ltxml index 50295cbf0..99a4962d2 100644 --- a/lib/LaTeXML/Engine/TeX_Glue.pool.ltxml +++ b/lib/LaTeXML/Engine/TeX_Glue.pool.ltxml @@ -84,21 +84,22 @@ DefConstructor('\hskip Glue', sub { reversion => sub { revertSkip(T_CS('\hskip'), $_[1]); }, attributeForm => sub { DimensionToSpaces($_[1]); }, enterHorizontal => 1, - properties => sub { (width => $_[1], isSpace => 1, isSkip => 1); }); + properties => sub { (width => $_[1], isSpace => 1, isSkip => 1); }); # We should be combining adjacent vskips (?) # And then add appropriate class/css for spacing to preceding element. DefConstructor('\vskip Glue', sub { - my ($document, $length, %props) = @_; - $length = $length->ptValue; - if ($length <= 0) { } # Or what!?!?!?! - elsif(($length < 4) && $document->isCloseable('ltx:p')) { - $document->closeElement('ltx:p'); } - elsif($document->isCloseable('ltx:para')) { - $document->closeElement('ltx:para'); } - return; }, + my ($document, $length, %props) = @_; + $length = $length->ptValue; + if ($length <= 0) { } # Or what!?!?!?! + elsif (($length < 4) && $document->isCloseable('ltx:p')) { + $document->closeElement('ltx:p'); } + elsif ($document->isCloseable('ltx:para')) { + $document->closeElement('ltx:para'); } + return; }, leaveHorizontal => 1, - properties => sub { (height => $_[1], isSpace => 1, isSkip => 1, isVerticalSpace => 1); }); + properties => sub { (height => $_[1], width => Dimension(0), depth => Dimension(0), + isSpace => 1, isSkip => 1, isVerticalSpace => 1); }); # Remove skip, if last on LIST DefPrimitiveI('\unskip', undef, sub { @@ -125,12 +126,12 @@ DefPrimitiveI('\hss', undef, undef, enterHorizontal => 1,); DefPrimitiveI('\hfilneg', undef, undef, enterHorizontal => 1); DefPrimitiveI('\hfil', undef, sub { - Box(' ', undef, undef, T_CS('\hfil'), isSpace => 1, isFill => 1); }, + Box(' ', undef, undef, T_CS('\hfil'), isSpace => 1, isFill => 1); }, enterHorizontal => 1); ### Box("\x{200B}", undef, undef, T_CS('\hfil'), isSpace => 1, isFill => 1); }); ### Box("\x{200A}", undef, undef, T_CS('\hfil'), isSpace => 1, isFill => 1); }); DefPrimitiveI('\hfill', undef, sub { - Box(' ', undef, undef, T_CS('\hfill'), isSpace => 1, isFill => 1); }, + Box(' ', undef, undef, T_CS('\hfill'), isSpace => 1, isFill => 1); }, enterHorizontal => 1); #### Box("\x{200B}", undef, undef, T_CS('\hfill'), isSpace => 1, isFill => 1); }); ### Box("\x{200A}", undef, undef, T_CS('\hfill'), isSpace => 1, isFill => 1); }); From 86cc33a531c2ad870767859190202717fdb538b2 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 28 May 2026 17:31:44 -0400 Subject: [PATCH 27/31] reindent graphics.sty --- lib/LaTeXML/Package/graphics.sty.ltxml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/LaTeXML/Package/graphics.sty.ltxml b/lib/LaTeXML/Package/graphics.sty.ltxml index f3878e1ea..45cb60871 100644 --- a/lib/LaTeXML/Package/graphics.sty.ltxml +++ b/lib/LaTeXML/Package/graphics.sty.ltxml @@ -63,9 +63,9 @@ DefParameterType('GraphixDimensions', sub { sub graphics_scaledbox_props { my ($box, $xscale, $yscale) = @_; my ($w, $h, $d) = $box->getSize; - my ($sw, $sh, $sd) = + my ($sw, $sh, $sd) = ($w && $w->multiply($xscale), $h && $h->multiply($yscale), $d && $d->multiply($yscale)); - my $H = $h && ($d ? $h->add($d) : $h); + my $H = $h && ($d ? $h->add($d) : $h); my $sH = $sh && ($sd ? $sh->add($sd) : $sh); return ( box => $box, @@ -78,7 +78,7 @@ sub graphics_scaledbox_props { depth => $sd, totalheight => $h && ($d ? $h->add($d) : $h)->multiply($yscale), xtranslate => $sw && $w && $sw->subtract($w)->multiply(+0.5), - ytranslate => $sH && $H && $sH->subtract($H)->multiply(-0.5) ); } + ytranslate => $sH && $H && $sH->subtract($H)->multiply(-0.5)); } sub graphics_scaledbox_insert { my ($document, %props) = @_; From 7229b26095a649d8bc97e7507e524299c378fe02 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 28 May 2026 17:49:12 -0400 Subject: [PATCH 28/31] parbox doesn't indent paragraphs --- lib/LaTeXML/Engine/latex_constructs.pool.ltxml | 4 +++- lib/LaTeXML/resources/CSS/LaTeXML.css | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml index d9b8df62a..5df0b57ad 100644 --- a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml +++ b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml @@ -4756,7 +4756,9 @@ Let('\lx@parboxnewline', '\lx@newline'); # Obsolete, but in case still used # NOTE: There are 2 extra arguments (See LaTeX Companion, p.866) # for height and inner-pos. We're ignoring inner-pos, for now, though. DefMacro('\parbox[] [] [] {Dimension}{}', -'\lx@hidden@bgroup\hsize=#4\textwidth\hsize\columnwidth\hsize\ifx.#2.\lx@parbox[#1]{#4}{#5}\else\lx@parbox[#1][#2][#3]{#4}{#5}\fi\lx@hidden@egroup'); + '\lx@hidden@bgroup\hsize=#4\textwidth\hsize\columnwidth\hsize' + . '\parindent\z@\parskip\z@skip' + . '\ifx.#2.\lx@parbox[#1]{#4}{#5}\else\lx@parbox[#1][#2][#3]{#4}{#5}\fi\lx@hidden@egroup'); DefConstructor('\lx@parbox[][Dimension] OptionalUndigested {Dimension} VBoxContents', sub { my ($document, $attachment, $b, $c, $width, $body, %props) = @_; diff --git a/lib/LaTeXML/resources/CSS/LaTeXML.css b/lib/LaTeXML/resources/CSS/LaTeXML.css index 758ff3d0c..aeafa34fd 100644 --- a/lib/LaTeXML/resources/CSS/LaTeXML.css +++ b/lib/LaTeXML/resources/CSS/LaTeXML.css @@ -336,7 +336,7 @@ dl.ltx_description dl.ltx_description dd { margin-left:3em; } max-width:0em; text-align:right; } */ .ltx_parbox { - text-indent:0em; + text-indent:0em !important; display: inline-block; } /* NOTE that it is CRITICAL to put position:relative outside & absolute inside!! From 43d048e7571169c2809e317f27fcfed57d2ba4dd Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 28 May 2026 17:54:51 -0400 Subject: [PATCH 29/31] Improve itemization sizing --- .../Engine/latex_constructs.pool.ltxml | 59 +++++++++---------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml index 5df0b57ad..3fef2ca39 100644 --- a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml +++ b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml @@ -1031,22 +1031,22 @@ DefMacro('\@dbltoplist', ''); DefMacro('\@dbldeferlist', ''); DefMacro('\@startcolumn', ''); # Style parameters from Fig. C.3, p.182 -DefRegister('\paperheight' => Dimension('11in')); -DefRegister('\paperwidth' => Dimension('8.5in')); -DefRegister('\textheight' => Dimension('550pt')); -DefRegister('\textwidth' => Dimension('345pt')); -DefRegister('\topmargin' => Dimension(0)); -DefRegister('\headheight' => Dimension(0)); -DefRegister('\headsep' => Dimension(0)); -DefRegister('\footskip' => Dimension(0)); -DefRegister('\footheight' => Dimension(0)); -DefRegister('\evensidemargin' => Dimension(0)); -DefRegister('\oddsidemargin' => Dimension(0)); -DefRegister('\marginparwidth' => Dimension(0)); -DefRegister('\marginparsep' => Dimension(0)); -DefRegister('\columnwidth' => Dimension('6in')); -DefRegister('\linewidth' => Dimension('6in')); -DefRegister('\baselinestretch' => Dimension(0)); +DefRegister('\paperheight' => Dimension('11in')); +DefRegister('\paperwidth' => Dimension('8.5in')); +DefRegister('\textheight' => Dimension('550pt')); +DefRegister('\textwidth' => Dimension('345pt')); +DefRegister('\topmargin' => Dimension(0)); +DefRegister('\headheight' => Dimension(0)); +DefRegister('\headsep' => Dimension(0)); +DefRegister('\footskip' => Dimension(0)); +DefRegister('\footheight' => Dimension(0)); +DefRegister('\evensidemargin' => Dimension(0)); +DefRegister('\oddsidemargin' => Dimension(0)); +DefRegister('\marginparwidth' => Dimension(0)); +DefRegister('\marginparsep' => Dimension(0)); +DefRegister('\columnwidth' => Dimension('6in')); +DefRegister('\linewidth' => Dimension('6in')); +DefMacro('\baselinestretch' => '1'); #====================================================================== # C.5.4 The Title Page and Abstract @@ -1352,7 +1352,8 @@ sub beginItemize { ResetCounter($usecounter); } ## return RefStepCounter('@itemize' . $listpostfix); } return (RefStepCounter('@itemize' . $listpostfix), - counter => $usecounter, series => $series); } # So end can save counter value + counter => $usecounter, series => $series, # So end can save counter value + padtop => LookupDimension('\topsep')); } # These counters are ONLY used for id's of ALL the various itemize, enumerate, etc elements # Only create the 1st level (so that binding style can start numbering 'within' appropriately) @@ -1367,12 +1368,9 @@ sub RefStepItemCounter { AssignValue(itemization_items => $n + 1); my %attr = (); my $sep = LookupDimension('\itemsep'); - if (($n > 0) && $sep && ($sep->valueOf != LookupDimension('\lx@default@itemsep')->valueOf)) { - $attr{itemsep} = $sep; - # this SHOULD pad the item, but \item is ONLY the marker, not the item content! -## $attr{padtop} = $sep; -## $attr{height} = Dimension('1em'); - } + if (($n > 0) && $sep) { + if ($sep->valueOf != LookupDimension('\lx@default@itemsep')->valueOf) { + $attr{itemsep} = $sep; } } if (defined $tag) { my @props = RefStepID($counter); if (IsEmpty($tag)) { # empty tag? @@ -1449,12 +1447,11 @@ sub setEnumerationStyle { return; } # End paragraphs, like \par, but only if within an item's text -DefConstructorI('\preitem@par', undef, sub { - my ($document, %props) = @_; - if (!$props{inPreamble} && !$document->getNodeQName($document->getElement(), 'ltx:itemize')) { - $document->maybeCloseElement('ltx:p'); - $document->maybeCloseElement('ltx:para'); } }, - alias => '\par'); +# Also, add a vskip, between for sizing +DefMacro('\preitem@par', sub { + return ((LookupValue('itemization_items') || 0) > 0 + ? (T_CS('\par'), Invocation(T_CS('\vskip'), T_CS('\itemsep'))) + : ()); }); # id, but NO refnum (et.al) attributes on itemize \item ... # unless the optional tag argument was given! @@ -2976,7 +2973,7 @@ sub defineNewTheorem { T_CS('\let'), T_CS('\the' . $counter), T_CS('\@empty'), Invocation(T_CS('\lx@make@tags'), T_OTHER($thmset)), T_END); - } } + } } else { %ctr = RefStepCounter($thmset); } } my $title = DigestText(Tokens(T_BEGIN, @@ -3324,7 +3321,7 @@ sub arrange_panels_and_breaks { my $block = $document->wrapNodes('ltx:block', $prev_node, $child); push(@row_contents, [$block, 'ltx:block', $prev_width + $child_width]); pop(@all_panels); push(@all_panels, $block); - } } + } } else { # otherwise keep the last panel as-is, and append a sibling push(@row_contents, $prev_panel); From 988c737069cfd7060465eee8e48f1b4e2bcf2e6f Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 28 May 2026 17:57:59 -0400 Subject: [PATCH 30/31] Use dumped vspace macros, when possible; implement more of setspace.sty --- lib/LaTeXML/Engine/latex_base.pool.ltxml | 68 +++++++++---------- .../Engine/latex_constructs.pool.ltxml | 3 - lib/LaTeXML/Package/setspace.sty.ltxml | 9 +-- 3 files changed, 38 insertions(+), 42 deletions(-) diff --git a/lib/LaTeXML/Engine/latex_base.pool.ltxml b/lib/LaTeXML/Engine/latex_base.pool.ltxml index 8a1ebce94..834ea5bfa 100644 --- a/lib/LaTeXML/Engine/latex_base.pool.ltxml +++ b/lib/LaTeXML/Engine/latex_base.pool.ltxml @@ -32,8 +32,8 @@ use List::Util qw(min max); # C.0 Prelminaries & Shorthands #====================================================================== -Let('\@pushfilename','\lx@pushfilename'); -Let('\@popfilename', '\lx@popfilename'); +Let('\@pushfilename', '\lx@pushfilename'); +Let('\@popfilename', '\lx@popfilename'); DefMacroI('\@ehc', undef, "I can't help"); @@ -139,18 +139,18 @@ DefMacro('\@xviipt', '17.28'); DefMacro('\@xxpt', '20.74'); DefMacro('\@xxvpt', '24.88'); # LaTeX 209 stuff -DefMacro('\vpt', '\edef\f@size{\@vpt}\rm'); -DefMacro('\vipt', '\edef\f@size{\@vipt}\rm'); -DefMacro('\viipt', '\edef\f@size{\@viipt}\rm'); -DefMacro('\viiipt', '\edef\f@size{\@viiipt}\rm'); -DefMacro('\ixpt', '\edef\f@size{\@ixpt}\rm'); -DefMacro('\xpt', '\edef\f@size{\@xpt}\rm'); -DefMacro('\xipt', '\edef\f@size{\@xipt}\rm'); -DefMacro('\xiipt', '\edef\f@size{\@xiipt}\rm'); -DefMacro('\xivpt', '\edef\f@size{\@xivpt}\rm'); -DefMacro('\xviipt', '\edef\f@size{\@xviipt}\rm'); -DefMacro('\xxpt', '\edef\f@size{\@xxpt}\rm'); -DefMacro('\xxvpt', '\edef\f@size{\@xxvpt}\rm'); +DefMacro('\vpt', '\edef\f@size{\@vpt}\rm'); +DefMacro('\vipt', '\edef\f@size{\@vipt}\rm'); +DefMacro('\viipt', '\edef\f@size{\@viipt}\rm'); +DefMacro('\viiipt', '\edef\f@size{\@viiipt}\rm'); +DefMacro('\ixpt', '\edef\f@size{\@ixpt}\rm'); +DefMacro('\xpt', '\edef\f@size{\@xpt}\rm'); +DefMacro('\xipt', '\edef\f@size{\@xipt}\rm'); +DefMacro('\xiipt', '\edef\f@size{\@xiipt}\rm'); +DefMacro('\xivpt', '\edef\f@size{\@xivpt}\rm'); +DefMacro('\xviipt', '\edef\f@size{\@xviipt}\rm'); +DefMacro('\xxpt', '\edef\f@size{\@xxpt}\rm'); +DefMacro('\xxvpt', '\edef\f@size{\@xxvpt}\rm'); #********************************************************************** # Basic \documentclass & \documentstyle @@ -268,8 +268,8 @@ DefMacro('\@restorepar', '\def\par{\@par}'); NewCounter('footnote'); DefMacroI('\thefootnote', undef, '\arabic{footnote}'); NewCounter('mpfootnote'); -DefMacroI('\thempfn', undef, '\thefootnote'); -DefMacroI('\thempfootnote', undef, '\arabic{mpfootnote}'); +DefMacroI('\thempfn', undef, '\thefootnote'); +DefMacroI('\thempfootnote', undef, '\arabic{mpfootnote}'); DefRegister('\footnotesep' => Dimension(0)); #====================================================================== # C.3.4 Accents and Special Symbols @@ -291,9 +291,9 @@ DefMacroI('\appendixesname', undef, 'Appendixes'); # C.4.3 Table of Contents #====================================================================== # Insert stubs that will be filled in during post processing. -DefMacroI('\contentsname', undef, 'Contents'); +DefMacroI('\contentsname', undef, 'Contents'); DefMacroI('\listfigurename', undef, 'List of Figures'); -DefMacroI('\listtablename', undef, 'List of Tables'); +DefMacroI('\listtablename', undef, 'List of Tables'); #====================================================================== # C.4.4 Style registers #====================================================================== @@ -357,11 +357,10 @@ DefMacro('\subparagraphmark{}', Tokens()); DefMacro('\@tabacckludge {}', '\csname\string#1\endcsname'); DefPrimitive('\DeclareTextAccent DefToken {}{}', sub { - ignoredDefinition('DeclareTextAccent', $_[1]); }); + ignoredDefinition('DeclareTextAccent', $_[1]); }); DefPrimitive('\DeclareTextAccentDefault{}{}', sub { ignoredDefinition('DeclareTextAccentDefault', $_[1]); }); - DefPrimitive('\DeclareTextComposite{}{}{}{}', sub { ignoredDefinition('DeclareTextComposite', $_[1]); }); DefPrimitive('\DeclareTextCompositeCommand{}{}{}{}', @@ -401,17 +400,17 @@ DefMacroI('\floatpagefraction', undef, "0.25"); NewCounter('dbltopnumber'); DefMacroI('\dbltopfraction', undef, "0.7"); DefMacroI('\dblfloatpagefraction', undef, "0.25"); -DefRegister('\floatsep' => Glue('12.0pt plus 2.0pt minus 2.0pt')); -DefRegister('\textfloatsep' => Glue('20.0pt plus 2.0pt minus 4.0pt')); -DefRegister('\intextsep' => Glue('12.0pt plus 2.0pt minus 2.0pt')); -DefRegister('\dblfloatsep' => Glue('12.0pt plus 2.0pt minus 2.0pt')); -DefRegister('\dbltextfloatsep' => Glue('20.0pt plus 2.0pt minus 4.0pt')); -DefRegister('\@fptop' => Glue(0)); -DefRegister('\@fpsep' => Glue(0)); -DefRegister('\@fpbot' => Glue(0)); -DefRegister('\@dblfptop' => Glue(0)); -DefRegister('\@dblfpsep' => Glue(0)); -DefRegister('\@dblfpbot' => Glue(0)); +DefRegister('\floatsep' => Glue('12.0pt plus 2.0pt minus 2.0pt')); +DefRegister('\textfloatsep' => Glue('20.0pt plus 2.0pt minus 4.0pt')); +DefRegister('\intextsep' => Glue('12.0pt plus 2.0pt minus 2.0pt')); +DefRegister('\dblfloatsep' => Glue('12.0pt plus 2.0pt minus 2.0pt')); +DefRegister('\dbltextfloatsep' => Glue('20.0pt plus 2.0pt minus 4.0pt')); +DefRegister('\@fptop' => Glue(0)); +DefRegister('\@fpsep' => Glue(0)); +DefRegister('\@fpbot' => Glue(0)); +DefRegister('\@dblfptop' => Glue(0)); +DefRegister('\@dblfpsep' => Glue(0)); +DefRegister('\@dblfpbot' => Glue(0)); Let('\topfigrule', '\relax'); Let('\botfigrule', '\relax'); Let('\dblfigrule', '\relax'); @@ -485,7 +484,6 @@ RawTeX(<<'EOL'); \DeclareRobustCommand\usebox[1]{\leavevmode\copy #1\relax} EOL - #********************************************************************** # C.14 Pictures and Color #********************************************************************** @@ -597,7 +595,6 @@ EoTeX #====================================================================== - RawTeX(<<'EOTeX'); \chardef\@xxxii=32 \mathchardef\@Mi=10001 @@ -802,8 +799,9 @@ RawTeX(<<'EOTeX'); \def\glb@settings{}% EOTeX - - +DefPrimitive('\addvspace {}', undef); +DefPrimitive('\addpenalty {}', undef); +DefPrimitiveI('\@endparenv'); #====================================================================== DefMacroI('\loggingall', undef, Tokens()); diff --git a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml index 3fef2ca39..0c2e2cb2b 100644 --- a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml +++ b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml @@ -4647,9 +4647,6 @@ DefPrimitive('\hspace OptionalMatch:* {Dimension}', sub { width => $length, isSpace => 1); }); DefMacro('\vspace OptionalMatch:* {}', '\vskip #2\relax'); -DefPrimitive('\addvspace {}', undef); -DefPrimitive('\addpenalty {}', undef); -DefPrimitiveI('\@endparenv'); # \hfill, \vfill diff --git a/lib/LaTeXML/Package/setspace.sty.ltxml b/lib/LaTeXML/Package/setspace.sty.ltxml index 54c5a61f5..bcf208c12 100644 --- a/lib/LaTeXML/Package/setspace.sty.ltxml +++ b/lib/LaTeXML/Package/setspace.sty.ltxml @@ -26,10 +26,11 @@ DefEnvironment("{onehalfspace}", "#body"); DefEnvironment("{doublespace}", "#body"); DefEnvironment("{spacing}{}", "#body"); -DefMacroI('\setstretch', '{}', ''); -DefMacroI('\SetSinglespace', '{}', ''); -DefMacroI('\setdisplayskipstretch', '{}', ''); -DefMacroI('\restore@spacing', undef, ''); +DefMacro('\setspace@singlespace', '1'); +DefMacro('\setstretch{}', '\def\baselinestretch{#1}'); +DefMacro('\SetSinglespace{}', '\def\setspace@singlespace{#1}'); +DefMacro('\setdisplayskipstretch{}', ''); +DefMacro('\restore@spacing', ''); #********************************************************************** 1; From 2d3c6db31314277a34e6c0776b7a63453331751e Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 28 May 2026 18:12:44 -0400 Subject: [PATCH 31/31] Set svg size in em units (viewbox will scale to fit that size), then set foreignObject fontsize which WILL get rescaled by svg to match browser's current fontsize (surprisingly); update xy test case accordingly --- lib/LaTeXML/Engine/TeX_Box.pool.ltxml | 5 +-- lib/LaTeXML/Package/pgfsys-latexml.def.ltxml | 7 +++- lib/LaTeXML/Package/xy.tex.ltxml | 42 +++++++++---------- t/graphics/xytest.xml | 44 ++++++++++---------- 4 files changed, 50 insertions(+), 48 deletions(-) diff --git a/lib/LaTeXML/Engine/TeX_Box.pool.ltxml b/lib/LaTeXML/Engine/TeX_Box.pool.ltxml index 11fa3ab1d..e7eaaea47 100644 --- a/lib/LaTeXML/Engine/TeX_Box.pool.ltxml +++ b/lib/LaTeXML/Engine/TeX_Box.pool.ltxml @@ -385,7 +385,8 @@ Tag('svg:foreignObject', autoOpen => 1, autoClose => 1, $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;'); + . '--ltx-fo-depth:' . $d->emValue(undef, $f) . 'em;' + . 'font-size:' . $f->getSize . 'pt;'); } }); sub isVAttached { @@ -418,8 +419,6 @@ 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}) { - $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); $document->absorb($contents); diff --git a/lib/LaTeXML/Package/pgfsys-latexml.def.ltxml b/lib/LaTeXML/Package/pgfsys-latexml.def.ltxml index 6d44b4072..8ab9a5db6 100644 --- a/lib/LaTeXML/Package/pgfsys-latexml.def.ltxml +++ b/lib/LaTeXML/Package/pgfsys-latexml.def.ltxml @@ -84,10 +84,13 @@ DefConstructor('\lxSVG@insertpicture{}', sub { $document->absorb($content); $document->closeElement('svg:g'); } else { + my $font = $props{_font}; $document->openElement('ltx:picture'); $document->openElement('svg:svg', version => "1.1", - width => $props{pxwidth}, height => $props{pxheight}, style => $props{style}, + width => $props{width}->emValue(2, $font) . 'em', + height => $props{height}->emValue(2, $font) . 'em', + style => $props{style}, viewBox => "$props{minx} $props{miny} $props{pxwidth} $props{pxheight}", overflow => "visible"); my $x0 = -(0 + $props{minx}); @@ -134,6 +137,8 @@ DefConstructor('\lxSVG@insertpicture{}', sub { $whatsit->setProperty(pxwidth => $w); $whatsit->setProperty(pxheight => $h); $whatsit->setProperty(style => "vertical-align:" . $base . "px") if $base; + $whatsit->setProperty(_font => $whatsit->getFont); + # or tikz macro (see corescopes) return; }, # \pgfpicture seems to make a 0 sized box, which throws off our postprocessor diff --git a/lib/LaTeXML/Package/xy.tex.ltxml b/lib/LaTeXML/Package/xy.tex.ltxml index cb1105ec2..410c44ff2 100644 --- a/lib/LaTeXML/Package/xy.tex.ltxml +++ b/lib/LaTeXML/Package/xy.tex.ltxml @@ -93,20 +93,18 @@ DefConstructor('\lx@xy@svgnested Digested', return (width => $w, height => $y1, depth => $y0->negate, transform => $transform); }); DefConstructor('\lx@xy@svg Digested', sub { -# "" -# . "" -# . "#1" -# . "", - my ($document,$content,%props) = @_; + my ($document, $content, %props) = @_; + my $font = $props{_font}; $document->openElement('ltx:picture'); $document->openElement('svg:svg', - version => "1.1", overflow => "visible", - width => $props{pxwidth}, height => $props{pxheight}, style => $props{style}, - viewBox => "$props{minx} $props{miny} $props{pxwidth} $props{pxheight}"); - $document->openElement('svg:g',transform => $props{transform}, _scopebegin => 1); + version => "1.1", overflow => "visible", + width => $props{width}->emValue(2, $font) . 'em', + height => $props{height}->emValue(2, $font) . 'em', + style => $props{style}, + viewBox => "$props{minx} $props{miny} $props{pxwidth} $props{pxheight}"); + $document->openElement('svg:g', transform => $props{transform}, _scopebegin => 1); addSVGDebuggingBox($document, - $props{x},$props{y},$props{width},$props{height},'#FF00FF') + $props{x}, $props{y}, $props{width}, $props{height}, '#FF00FF') if $LaTeXML::DEBUG{svg}; $document->absorb($content); $document->closeElement('svg:g'); @@ -118,26 +116,26 @@ DefConstructor('\lx@xy@svg Digested', sub { || [Dimension(0), Dimension(0), Dimension(0), Dimension(0)] }; Debug("XY: " . ToString($x0) . ' ' . ToString($y0) . ' ' . ToString($x1) . ' ' . ToString($y1)) if $LaTeXML::DEBUG{svg_verbose}; - my $w = $x1->subtract($x0); - my $h = $y1->subtract($y0); - if($w->valueOf < 0){ # Rarely, the range hasn't actually been set?!? - $x0 = $x1 = Dimension(0); } - if($h->valueOf < 0){ - $y0 = $y1 = Dimension(0); } + my $w = $x1->subtract($x0); + my $h = $y1->subtract($y0); + if ($w->valueOf < 0) { # Rarely, the range hasn't actually been set?!? + $x0 = $x1 = Dimension(0); } + if ($h->valueOf < 0) { + $y0 = $y1 = Dimension(0); } my $x = $x0->negate; my $y = $y1->subtract($y0); my $minx = $x->pxValue; my $miny = $y0->negate->pxValue; my $transform = "matrix(1 0 0 -1 " . $x->pxValue . ' ' . $y->pxValue . ')'; - my $style = ($miny ? "vertical-align:".(-$miny)."px" : undef); - my $pxwidth = max($w->pxValue,1); - my $pxheight = max($h->pxValue,1); + my $style = ($miny ? "vertical-align:" . (-$miny) . "px" : undef); + my $pxwidth = max($w->pxValue, 1); + my $pxheight = max($h->pxValue, 1); Debug("XY size: " . ToString($w) . ' x ' . ToString($h) . ' + ' . 0 . ' @ ' . ToString($x) . ' x ' . ToString($y)) if $LaTeXML::DEBUG{svg_verbose}; return (x => $x0, y => $y0, width => $w, height => $h, - pxwidth => $pxwidth, pxheight => $pxheight, - minx => $minx, miny => $miny, style => $style, + pxwidth => $pxwidth, pxheight => $pxheight, + minx => $minx, miny => $miny, style => $style, transform => $transform); }); DefPrimitive('\lx@xy@capturerange', sub { diff --git a/t/graphics/xytest.xml b/t/graphics/xytest.xml index b7cf6c016..05c3a0d63 100644 --- a/t/graphics/xytest.xml +++ b/t/graphics/xytest.xml @@ -8,17 +8,17 @@ - + - + A - + B @@ -34,17 +34,17 @@ - + - + U - + y @@ -63,7 +63,7 @@ - + x @@ -75,7 +75,7 @@ - + @@ -91,7 +91,7 @@ - + q @@ -105,7 +105,7 @@ - + p @@ -118,7 +118,7 @@ - + X @@ -126,7 +126,7 @@ - + f @@ -139,7 +139,7 @@ - + Y @@ -147,7 +147,7 @@ - + g @@ -160,7 +160,7 @@ - + Z @@ -179,10 +179,10 @@ = x - + - + A @@ -210,21 +210,21 @@ - + B - + C - D + D @@ -247,13 +247,13 @@ [ - + - +
@@ -100,7 +100,7 @@ Testing and inside . - + Algorithm 1 1 diff --git a/t/fonts/sizes.xml b/t/fonts/sizes.xml index 3f38097e2..84742c1db 100644 --- a/t/fonts/sizes.xml +++ b/t/fonts/sizes.xml @@ -181,7 +181,7 @@ Text: ‘ hop - ’ is 469.75499pt x 6.94444pt + 1.94444pt. + ’ is 345.0pt x 6.94444pt + 1.94444pt.
Testing and inside .
Text: ‘ hop - ’ is 469.75499pt x 6.94444pt + 1.94444pt.
hop
Text: ‘ diff --git a/t/structure/autoref.xml b/t/structure/autoref.xml index 6bc14ec2a..3d3d6e2e9 100644 --- a/t/structure/autoref.xml +++ b/t/structure/autoref.xml @@ -60,7 +60,7 @@ Table 1.1Table caption The Table - + Figure 1.2 Figure 1.2 diff --git a/t/structure/figure_grids.xml b/t/structure/figure_grids.xml index a51e090dd..724f16070 100644 --- a/t/structure/figure_grids.xml +++ b/t/structure/figure_grids.xml @@ -179,7 +179,7 @@ (a) 1(a) - + (a)subcaption (a)subcaption @@ -188,7 +188,7 @@ (b) 1(b) - + (b)subcaption (b)subcaption @@ -201,7 +201,7 @@ (a) 2(a) - + (a)subcaption (a)subcaption @@ -211,7 +211,7 @@ (b) 2(b) - + (b)subcaption (b)subcaption @@ -224,7 +224,7 @@ (a) 3(a) - + (a)subcaption (a)subcaption @@ -233,7 +233,7 @@ (b) 3(b) - + (b)subcaption (b)subcaption @@ -243,7 +243,7 @@ (c) 3(c) - + (c)subcaption (c)subcaption @@ -252,7 +252,7 @@ (d) 3(d) - + (d)subcaption (d)subcaption @@ -265,7 +265,7 @@ (a) 4(a) - + (a)subcaption (a)subcaption @@ -274,7 +274,7 @@ (b) 4(b) - + (b)subcaption (b)subcaption @@ -283,7 +283,7 @@ (c) 4(c) - + (c)subcaption (c)subcaption @@ -293,7 +293,7 @@ (d) 4(d) - + (d)subcaption (d)subcaption @@ -302,7 +302,7 @@ (e) 4(e) - + (e)subcaption (e)subcaption @@ -311,7 +311,7 @@ (f) 4(f) - + (f)subcaption (f)subcaption @@ -321,7 +321,7 @@ (g) 4(g) - + (g)subcaption (g)subcaption @@ -330,7 +330,7 @@ (h) 4(h) - + (h)subcaption (h)subcaption @@ -339,7 +339,7 @@ (i) 4(i) - + (i)subcaption (i)subcaption @@ -352,7 +352,7 @@ (a) 5(a) - + (a)subcaption (a)subcaption @@ -361,7 +361,7 @@ (b) 5(b) - + (b)subcaption (b)subcaption @@ -370,7 +370,7 @@ (c) 5(c) - + (c)subcaption (c)subcaption @@ -379,7 +379,7 @@ (d) 5(d) - + (d)subcaption (d)subcaption @@ -389,7 +389,7 @@ (e) 5(e) - + (e)subcaption (e)subcaption @@ -398,7 +398,7 @@ (f) 5(f) - + (f)subcaption (f)subcaption @@ -407,7 +407,7 @@ (g) 5(g) - + (g)subcaption (g)subcaption @@ -416,7 +416,7 @@ (h) 5(h) - + (h)subcaption (h)subcaption @@ -426,7 +426,7 @@ (i) 5(i) - + (i)subcaption (i)subcaption @@ -435,7 +435,7 @@ (j) 5(j) - + (j)subcaption (j)subcaption @@ -444,7 +444,7 @@ (k) 5(k) - + (k)subcaption (k)subcaption @@ -453,7 +453,7 @@ (l) 5(l) - + (l)subcaption (l)subcaption @@ -463,7 +463,7 @@ (m) 5(m) - + (m)subcaption (m)subcaption @@ -472,7 +472,7 @@ (n) 5(n) - + (n)subcaption (n)subcaption @@ -481,7 +481,7 @@ (o) 5(o) - + (o)subcaption (o)subcaption @@ -490,7 +490,7 @@ (p) 5(p) - + (p)subcaption (p)subcaption @@ -503,7 +503,7 @@ (a) 6(a) - + (a)subcaption (a)subcaption @@ -512,7 +512,7 @@ (b) 6(b) - + (b)subcaption (b)subcaption @@ -521,7 +521,7 @@ (c) 6(c) - + (c)subcaption (c)subcaption @@ -530,7 +530,7 @@ (d) 6(d) - + (d)subcaption (d)subcaption @@ -540,7 +540,7 @@ (e) 6(e) - + (e)subcaption (e)subcaption @@ -549,7 +549,7 @@ (f) 6(f) - + (f)subcaption (f)subcaption @@ -558,7 +558,7 @@ (g) 6(g) - + (g)subcaption (g)subcaption @@ -567,7 +567,7 @@ (h) 6(h) - + (h)subcaption (h)subcaption @@ -580,7 +580,7 @@ (a) 7(a) - + (a)subcaption (a)subcaption @@ -589,7 +589,7 @@ (b) 7(b) - + (b)subcaption (b)subcaption @@ -599,7 +599,7 @@ (c) 7(c) - + (c)subcaption (c)subcaption @@ -608,7 +608,7 @@ (d) 7(d) - + (d)subcaption (d)subcaption @@ -618,7 +618,7 @@ (e) 7(e) - + (e)subcaption (e)subcaption @@ -627,7 +627,7 @@ (f) 7(f) - + (f)subcaption (f)subcaption @@ -637,7 +637,7 @@ (g) 7(g) - + (g)subcaption (g)subcaption @@ -646,7 +646,7 @@ (h) 7(h) - + (h)subcaption (h)subcaption @@ -659,7 +659,7 @@ (a) 8(a) - + (a)subcaption (a)subcaption @@ -668,7 +668,7 @@ (b) 8(b) - + (b)subcaption (b)subcaption @@ -677,7 +677,7 @@ (c) 8(c) - + (c)subcaption (c)subcaption @@ -686,7 +686,7 @@ (d) 8(d) - + (d)subcaption (d)subcaption @@ -695,7 +695,7 @@ (e) 8(e) - + (e)subcaption (e)subcaption @@ -705,7 +705,7 @@ (f) 8(f) - + (f)subcaption (f)subcaption @@ -714,7 +714,7 @@ (g) 8(g) - + (g)subcaption (g)subcaption @@ -723,7 +723,7 @@ (h) 8(h) - + (h)subcaption (h)subcaption @@ -732,7 +732,7 @@ (i) 8(i) - + (i)subcaption (i)subcaption @@ -741,7 +741,7 @@ (j) 8(j) - + (j)subcaption (j)subcaption @@ -751,7 +751,7 @@ (k) 8(k) - + (k)subcaption (k)subcaption @@ -760,7 +760,7 @@ (l) 8(l) - + (l)subcaption (l)subcaption @@ -769,7 +769,7 @@ (m) 8(m) - + (m)subcaption (m)subcaption @@ -778,7 +778,7 @@ (n) 8(n) - + (n)subcaption (n)subcaption @@ -787,7 +787,7 @@ (o) 8(o) - + (o)subcaption (o)subcaption @@ -797,7 +797,7 @@ (p) 8(p) - + (p)subcaption (p)subcaption @@ -806,7 +806,7 @@ (q) 8(q) - + (q)subcaption (q)subcaption @@ -815,7 +815,7 @@ (r) 8(r) - + (r)subcaption (r)subcaption @@ -824,7 +824,7 @@ (s) 8(s) - + (s)subcaption (s)subcaption @@ -833,7 +833,7 @@ (t) 8(t) - + (t)subcaption (t)subcaption @@ -843,7 +843,7 @@ (u) 8(u) - + (u)subcaption (u)subcaption @@ -852,7 +852,7 @@ (v) 8(v) - + (v)subcaption (v)subcaption @@ -861,7 +861,7 @@ (w) 8(w) - + (w)subcaption (w)subcaption @@ -870,7 +870,7 @@ (x) 8(x) - + (x)subcaption (x)subcaption @@ -879,7 +879,7 @@ (y) 8(y) - + (y)subcaption (y)subcaption @@ -892,7 +892,7 @@ (a) 9(a) - + (a)subcaption (a)subcaption @@ -901,7 +901,7 @@ (b) 9(b) - + (b)subcaption (b)subcaption @@ -910,7 +910,7 @@ (c) 9(c) - + (c)subcaption (c)subcaption @@ -919,7 +919,7 @@ (d) 9(d) - + (d)subcaption (d)subcaption @@ -928,7 +928,7 @@ (e) 9(e) - + (e)subcaption (e)subcaption @@ -937,7 +937,7 @@ (f) 9(f) - + (f)subcaption (f)subcaption @@ -947,7 +947,7 @@ (g) 9(g) - + (g)subcaption (g)subcaption @@ -956,7 +956,7 @@ (h) 9(h) - + (h)subcaption (h)subcaption @@ -965,7 +965,7 @@ (i) 9(i) - + (i)subcaption (i)subcaption @@ -974,7 +974,7 @@ (j) 9(j) - + (j)subcaption (j)subcaption @@ -983,7 +983,7 @@ (k) 9(k) - + (k)subcaption (k)subcaption @@ -992,7 +992,7 @@ (l) 9(l) - + (l)subcaption (l)subcaption @@ -1002,7 +1002,7 @@ (m) 9(m) - + (m)subcaption (m)subcaption @@ -1011,7 +1011,7 @@ (n) 9(n) - + (n)subcaption (n)subcaption @@ -1020,7 +1020,7 @@ (o) 9(o) - + (o)subcaption (o)subcaption @@ -1029,7 +1029,7 @@ (p) 9(p) - + (p)subcaption (p)subcaption @@ -1038,7 +1038,7 @@ (q) 9(q) - + (q)subcaption (q)subcaption @@ -1047,7 +1047,7 @@ (r) 9(r) - + (r)subcaption (r)subcaption @@ -1057,7 +1057,7 @@ (s) 9(s) - + (s)subcaption (s)subcaption @@ -1066,7 +1066,7 @@ (t) 9(t) - + (t)subcaption (t)subcaption @@ -1075,7 +1075,7 @@ (u) 9(u) - + (u)subcaption (u)subcaption @@ -1084,7 +1084,7 @@ (v) 9(v) - + (v)subcaption (v)subcaption @@ -1093,7 +1093,7 @@ (w) 9(w) - + (w)subcaption (w)subcaption @@ -1102,7 +1102,7 @@ (x) 9(x) - + (x)subcaption (x)subcaption @@ -1112,7 +1112,7 @@ (y) 9(y) - + (y)subcaption (y)subcaption @@ -1121,7 +1121,7 @@ (z) 9(z) - + (z)subcaption (z)subcaption @@ -1130,7 +1130,7 @@ (aa) 9(aa) - + (aa)subcaption (aa)subcaption @@ -1139,7 +1139,7 @@ (ab) 9(ab) - + (ab)subcaption (ab)subcaption @@ -1148,7 +1148,7 @@ (ac) 9(ac) - + (ac)subcaption (ac)subcaption @@ -1157,7 +1157,7 @@ (ad) 9(ad) - + (ad)subcaption (ad)subcaption @@ -1167,7 +1167,7 @@ (ae) 9(ae) - + (ae)subcaption (ae)subcaption @@ -1176,7 +1176,7 @@ (af) 9(af) - + (af)subcaption (af)subcaption @@ -1185,7 +1185,7 @@ (ag) 9(ag) - + (ag)subcaption (ag)subcaption @@ -1194,7 +1194,7 @@ (ah) 9(ah) - + (ah)subcaption (ah)subcaption @@ -1203,7 +1203,7 @@ (ai) 9(ai) - + (ai)subcaption (ai)subcaption @@ -1212,7 +1212,7 @@ (aj) 9(aj) - + (aj)subcaption (aj)subcaption From 423ed3a62bc802374eac1275d298e961137bf22c Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Wed, 29 Apr 2026 17:50:29 -0400 Subject: [PATCH 07/31] Make sure arg of \underline,\overline are in restricted_horizontal; adjust size to account for lines --- lib/LaTeXML/Engine/TeX_Math.pool.ltxml | 22 ++++++++++++++----- .../Engine/latex_constructs.pool.ltxml | 6 ++--- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/lib/LaTeXML/Engine/TeX_Math.pool.ltxml b/lib/LaTeXML/Engine/TeX_Math.pool.ltxml index c18246607..2f81ba7a4 100644 --- a/lib/LaTeXML/Engine/TeX_Math.pool.ltxml +++ b/lib/LaTeXML/Engine/TeX_Math.pool.ltxml @@ -300,7 +300,7 @@ sub cleanup_XMText { $document->replaceNode($m, map { $_->childNodes } $m->childNodes); } else { # Otherwise, wrap whatever it is in an XMText $document->wrapNodes('ltx:XMText', $m); } - } } } + } } } # And now we don't need the XMText any more. foreach my $attr ($textnode->attributes) { # Copy the child's attributes (should Merge!!) $table->setAttribute($attr->nodeName => $attr->getValue); } @@ -423,7 +423,7 @@ sub scriptHandler { my $c = (($op eq 'SUPERSCRIPT') ? '^' : '_'); Error('unexpected', $c, $stomach, "Script $c can only appear in math mode"); return Box($c, undef, undef, (($op eq 'SUPERSCRIPT') ? T_SUPER : T_SUB)); - } } +} } DefPrimitiveI(T_SUPER, undef, sub { scriptHandler($_[0], 'SUPERSCRIPT'); }); DefPrimitiveI(T_SUB, undef, sub { scriptHandler($_[0], 'SUBSCRIPT'); }); @@ -544,7 +544,7 @@ DefRewrite(xpath => 'descendant::ltx:Math[child::ltx:XMath[child::ltx:XMApp[' . elsif ($role eq 'FLOATSUBSCRIPT') { $document->insertElement('ltx:sub', $text); return; } - } } } } + } } } } # should never happen, but just in case: Info("rewrite", "footnotemark", "Failed to find floating node in: " . $math->toString(1)); $document->getNode->appendChild($math); @@ -819,7 +819,7 @@ sub augmentDelimiterProperties { if (exists $$entry{char}) { # replace the char content! if (my $char = $$entry{char}) { $delim->firstChild->setData($char); } } - } } + } } return; } # Useful for afterConstruct of delimiter sizing macros (eg. \bigl) @@ -953,13 +953,23 @@ DefMath('\lx@math@overline{}', UTF(0xAF), name => 'overline', alias => '\overline'); DefConstructor('\lx@text@overline{}', "#1", - enterHorizontal => 1); + enterHorizontal => 1, mode => 'restricted_horizontal', + sizer => sub { + my $text = $_[0]->getArg(1); + my ($w, $h, $d) = $text->getSize; + my $pad = Dimension($text->getFont->getEXHeight); # WAG + return ($w, $h->add($pad), $d); }); DefMath('\lx@math@underline{}', UTF(0xAF), operator_role => 'UNDERACCENT', operator_stretchy => 'true', name => 'underline', alias => '\underline'); DefConstructor('\lx@text@underline{}', "#1", - enterHorizontal => 1); + enterHorizontal => 1, mode => 'restricted_horizontal', + sizer => sub { + my $text = $_[0]->getArg(1); + my ($w, $h, $d) = $text->getSize; + my $pad = Dimension($text->getFont->getEXHeight); # WAG + return ($w, $h, $d->add($pad)); }); DefMath('\lx@math@overrightarrow{}', "\x{2192}", operator_role => 'OVERACCENT', operator_stretchy => 'true', name => 'overrightarrow', alias => '\overrightarrow'); diff --git a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml index 98497a144..d1a4f234b 100644 --- a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml +++ b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml @@ -1842,7 +1842,7 @@ DefConstructor('\@internal@math@verb{} Undigested {}', DefConstructor('\@internal@text@verb{} Undigested {}', "#3", font => { family => 'typewriter', series => 'medium', shape => 'upright' }, - enterHorizontal => 1, + mode => 'restricted_horizontal', enterHorizontal => 1, beforeConstruct => sub { my ($doc, $whatsit) = @_; $doc->canContain($doc->getElement, '#PCDATA') || $doc->openElement('ltx:p'); }, @@ -4182,8 +4182,8 @@ DefConstructor('\@@cite []{}', "#2#3#4", - enterHorizontal => 1, - properties => sub { (bibrefs => CleanBibKey($_[2]), + mode => 'restricted_horizontal', enterHorizontal => 1, + properties => sub { (bibrefs => CleanBibKey($_[2]), separator => DigestText(LookupValue('CITE_SEPARATOR')), yyseparator => DigestText(LookupValue('CITE_YY_SEPARATOR')), bibunit => LookupValue('CITE_UNIT')); }); From 2bae3da85de535120350c2e4e6d00c3d2cf95ce8 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Wed, 29 Apr 2026 17:52:41 -0400 Subject: [PATCH 08/31] Make ltx_verbatim nowrap; hack fancyvrb to use that css class --- lib/LaTeXML/Package/fancyvrb.sty.ltxml | 4 ++++ lib/LaTeXML/resources/CSS/LaTeXML.css | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/LaTeXML/Package/fancyvrb.sty.ltxml b/lib/LaTeXML/Package/fancyvrb.sty.ltxml index e414f0bcc..69e3afdac 100644 --- a/lib/LaTeXML/Package/fancyvrb.sty.ltxml +++ b/lib/LaTeXML/Package/fancyvrb.sty.ltxml @@ -19,6 +19,10 @@ use LaTeXML::Package; InputDefinitions('fancyvrb', type => 'sty', noltxml => 1); +# Hack internals to add css class ltx_nowrap +Let('\lx@save@FancyVerbFormatLine', '\FancyVerbFormatLine'); +DefMacro('\FancyVerbFormatLine{}', + '\@ADDCLASS{ltx_verbatim}\lx@save@FancyVerbFormatLine{#1}'); #********************************************************************** 1; diff --git a/lib/LaTeXML/resources/CSS/LaTeXML.css b/lib/LaTeXML/resources/CSS/LaTeXML.css index 5efd8d018..e371a1737 100644 --- a/lib/LaTeXML/resources/CSS/LaTeXML.css +++ b/lib/LaTeXML/resources/CSS/LaTeXML.css @@ -445,7 +445,7 @@ span.ltx_framed { display:inline-block; text-indent:0; } /* avoid padding/ /*====================================================================== Misc */ /* .ltx_verbatim*/ -.ltx_verbatim { text-align:left; } +.ltx_verbatim { text-align:left; white-space:nowrap; } /*====================================================================== Meta stuff, footnotes */ .ltx_note_content { display:none; } From 251a24bc0ffd833d3b7932761abbb5eef2290d68 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 30 Apr 2026 18:04:33 -0400 Subject: [PATCH 09/31] Make \lx@tag and friends compute their size --- lib/LaTeXML/Engine/Base_Utility.pool.ltxml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/LaTeXML/Engine/Base_Utility.pool.ltxml b/lib/LaTeXML/Engine/Base_Utility.pool.ltxml index 65951b05d..bbb196daa 100644 --- a/lib/LaTeXML/Engine/Base_Utility.pool.ltxml +++ b/lib/LaTeXML/Engine/Base_Utility.pool.ltxml @@ -994,17 +994,22 @@ sub removeEmptyElement { DefConstructor('\lx@tag[][][]{}', "#4", mode => 'restricted_horizontal', + sizer => '#4', afterConstruct => \&removeEmptyElement); # \lx@tag@intags{role}{stuff} DefConstructor('\lx@tag@intags[]{}', "#2", mode => 'restricted_horizontal', + sizer => '#2', beforeDigest => sub { neutralizeFont() }, afterConstruct => \&removeEmptyElement); DefConstructor('\lx@tags{}', "#1", + sizer => sub { + my @tags = $_[0]->getArg(1)->unlist; # Arbitarily, only size the 1st ltx:tag. + ($tags[0] ? $tags[0]->getSize : (Dimension(0), Dimension(0), Dimension(0))); }, afterConstruct => \&removeEmptyElement); #---------------------------------------------------------------------- From daa9dbfeb64a5e97ca23e4c386b97ff3a3277e08 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 30 Apr 2026 18:05:22 -0400 Subject: [PATCH 10/31] soul's commands should process their arg in restricted_horizontal --- lib/LaTeXML/Package/soul.sty.ltxml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/LaTeXML/Package/soul.sty.ltxml b/lib/LaTeXML/Package/soul.sty.ltxml index 4fc1bf4ed..f6087dc08 100644 --- a/lib/LaTeXML/Package/soul.sty.ltxml +++ b/lib/LaTeXML/Package/soul.sty.ltxml @@ -68,7 +68,7 @@ sub getSOULcolor { # (should set framecolor from \setulcolor) DefConstructor('\textul{}', "#1", - enterHorizontal => 1, + enterHorizontal => 1, mode => 'restricted_horizontal', properties => { framecolor => sub { getSOULcolor('soul_ul_color'); } }); # Customizing underlines @@ -85,7 +85,7 @@ DefMacro('\setuloverlap{Dimension}', undef); # but then how to make framecolor end up as text-decoration-color? DefConstructor('\textst{}', "#1", - enterHorizontal => 1, + enterHorizontal => 1, mode => 'restricted_horizontal', properties => { framecolor => sub { my $framecolor = getSOULcolor('soul_strike_color'); return $framecolor ? "text-decoration-color:" . ($framecolor->toAttribute()) . ";" : ""; } }); @@ -97,7 +97,7 @@ DefPrimitive('\setstcolor{}', sub { AssignValue(soul_strike_color => $_[1]); }); DefMacro('\texthl', '\@ifpackageloaded{color}{\lx@texthl@color}{\textul}'); DefConstructor('\lx@texthl@color{}', "#1", - enterHorizontal => 1, + enterHorizontal => 1, mode => 'restricted_horizontal', bounded => 1, beforeDigest => sub { MergeFont(background => getSOULcolor('soul_hl_color')); }); # Customizing highlight AssignValue(soul_hl_color => 'yellow'); From 58736fe493488abeacda1543430b6ef286271628 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 30 Apr 2026 18:29:16 -0400 Subject: [PATCH 11/31] Make sure captions arg processed as paragraph mode (horizontal), and have reasonable sizing --- lib/LaTeXML/Engine/latex_constructs.pool.ltxml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml index d1a4f234b..3f57de830 100644 --- a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml +++ b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml @@ -3358,11 +3358,25 @@ Tag('ltx:table', afterClose => sub { BuildPanelsAndID(@_, 'tab'); }); # These may need to float up to where they're allowed, # or they may need to close or similar. +# These (should) appear in an internal_vertical context, +# BUT the arg should be processed to a horizontal (paragraph) List! DefConstructor('\@@caption{}', "^^#1", - sizer => 0, mode => 'restricted_horizontal', + sizer => '#1', + beforeDigest => sub { + $_[0]->beginMode('internal_vertical'); + $_[0]->enterHorizontal; }, + afterDigest => sub { + $_[0]->leaveHorizontal_internal; + $_[0]->endMode('internal_vertical') }, ); DefConstructor('\@@toccaption{}', "^^#1", - sizer => 0, mode => 'restricted_horizontal', + sizer => 0, + beforeDigest => sub { + $_[0]->beginMode('internal_vertical'); + $_[0]->enterHorizontal; }, + afterDigest => sub { + $_[0]->leaveHorizontal_internal; + $_[0]->endMode('internal_vertical') }, ); sub beforeFloat { From 1abcf815805470bf964fb87a9d9a03757183123e Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 30 Apr 2026 18:29:58 -0400 Subject: [PATCH 12/31] Update tests for caption change --- t/graphics/graphrot.xml | 2 +- t/structure/figures.xml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/t/graphics/graphrot.xml b/t/graphics/graphrot.xml index b4df83285..9cb6f16ea 100644 --- a/t/graphics/graphrot.xml +++ b/t/graphics/graphrot.xml @@ -395,7 +395,7 @@ the whales Save the whale Save the + Table 5 Table 5 diff --git a/t/structure/figures.xml b/t/structure/figures.xml index ad3567cbe..c4a990cce 100644 --- a/t/structure/figures.xml +++ b/t/structure/figures.xml @@ -52,6 +52,7 @@ (a)This is a figure (a)This is a figure + (b) @@ -105,6 +106,7 @@ and collectively as . (a)This is a figure (a)This is a figure + (b) @@ -133,6 +135,7 @@ and collectively as . (a)This is a table [Tabular One] + (b) @@ -188,6 +191,7 @@ and collectively as . (a)This is a table [Tabular One] + (b) From 53489cbb12804364d0ca93f81f5969c66817941c Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 30 Apr 2026 18:50:30 -0400 Subject: [PATCH 13/31] Extract internals of stomach->digestNextBody to stomach->digestUntil which does NOT rebind LaTeXML::LIST, which is needed for digesting T_BEGIN --- lib/LaTeXML/Core/Stomach.pm | 15 ++++++++++++--- lib/LaTeXML/Engine/TeX_Box.pool.ltxml | 17 +++++++++-------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/lib/LaTeXML/Core/Stomach.pm b/lib/LaTeXML/Core/Stomach.pm index dfed00105..a5b5be499 100644 --- a/lib/LaTeXML/Core/Stomach.pm +++ b/lib/LaTeXML/Core/Stomach.pm @@ -90,12 +90,21 @@ sub getScriptLevel { # It puts a lot of cruft in Gullet; Should we just create a new Gullet? sub digestNextBody { + my ($self, $terminal) = @_; + no warnings 'recursion'; + local @LaTeXML::LIST = (); + digestUntil($self, $terminal); + return @LaTeXML::LIST; } + +# This method digests content until $terminal or closing initial mode, +# pushing onto @LaTeXML::LIST. +# But, unlike digestNextBody, it does NOT bind @LaTeXML::LIST, nor return anything. +sub digestUntil { my ($self, $terminal) = @_; no warnings 'recursion'; my $startloc = getLocator($self); my $initdepth = scalar(@{ $$self{boxing} }); my $token; - local @LaTeXML::LIST = (); my $alignment = $STATE->lookupValue('Alignment'); my @aug = (); @@ -108,7 +117,7 @@ sub digestNextBody { # at least \over calls in here without the intent to passing through the alignment. # So if we already have some digested boxes available, return them here. $$self{gullet}->unread($token); - return @LaTeXML::LIST; } + return; } my @r = invokeToken($self, $token); push(@LaTeXML::LIST, @r); push(@aug, $token, @r); @@ -119,7 +128,7 @@ sub digestNextBody { "Got " . join("\n -- ", map { Stringify($_) } @aug)) if $terminal && !Equals($token, $terminal); push(@LaTeXML::LIST, Box()) unless $token; # Dummy `trailer' if none explicit. - return @LaTeXML::LIST; } + return; } # Digest a list of tokens independent from any current Gullet. # Typically used to digest arguments to primitives or constructors. diff --git a/lib/LaTeXML/Engine/TeX_Box.pool.ltxml b/lib/LaTeXML/Engine/TeX_Box.pool.ltxml index 496dfb06b..11fa3ab1d 100644 --- a/lib/LaTeXML/Engine/TeX_Box.pool.ltxml +++ b/lib/LaTeXML/Engine/TeX_Box.pool.ltxml @@ -27,18 +27,19 @@ $LaTeXML::DEBUG{svg} = 1 if $$LaTeXML::DEBUG{svg_verbose}; # These are actually TeX primitives, but we treat them as a Whatsit so they # remain in the constructed tree. -#DefConstructor('{','#body', beforeDigest=>sub{$_[0]->bgroup;}, captureBody=>1); -######DefPrimitive('{', sub { DefPrimitive(T_BEGIN, sub { my ($stomach) = @_; $stomach->bgroup; - my $open = Box(undef, undef, undef, T_BEGIN, isEmpty => 1, alignmentSkippable => 1); - my $ismath = $STATE->lookupValue('IN_MATH'); - my $mode = $STATE->lookupValue('MODE'); - my @body = $stomach->digestNextBody(); - return ($ismath ? List($open, @body, mode => $mode) : ($open, @body)); }); + my $open = Box(undef, undef, undef, T_BEGIN, isEmpty => 1, alignmentSkippable => 1); + my $mode = $STATE->lookupValue('MODE'); + if ($STATE->lookupValue('IN_MATH')) { # In math, we return a Math List + my @body = $stomach->digestNextBody(); + return List($open, @body, mode => $mode); } + else { # Else just digest directly to @LaTeXML::LIST + push(@LaTeXML::LIST, $open); + $stomach->digestUntil(); + return; } }); -#######DefPrimitive('}', sub { DefPrimitive(T_END, sub { my $f = LookupValue('font'); $_[0]->egroup; From 3cc1eb9fbaa95d77e5f34863ac85f7b2911412ec Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 7 May 2026 16:23:15 -0400 Subject: [PATCH 14/31] minipage defaults to justify alignment --- lib/LaTeXML/resources/CSS/LaTeXML.css | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/LaTeXML/resources/CSS/LaTeXML.css b/lib/LaTeXML/resources/CSS/LaTeXML.css index e371a1737..4f4578337 100644 --- a/lib/LaTeXML/resources/CSS/LaTeXML.css +++ b/lib/LaTeXML/resources/CSS/LaTeXML.css @@ -562,6 +562,7 @@ cite { font-style: normal; } .ltx_minipage { align-self: normal; display: inline-block; + text-align: justify; } .ltx_minipage > .ltx_graphics { max-width:100%; From 8835f238360041ab13476c2e87268466a2b6f0cb Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 7 May 2026 16:24:08 -0400 Subject: [PATCH 15/31] Pass context as block for svg:foreignObject to avoid bad span soup --- lib/LaTeXML/resources/XSLT/LaTeXML-picture-xhtml.xsl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/LaTeXML/resources/XSLT/LaTeXML-picture-xhtml.xsl b/lib/LaTeXML/resources/XSLT/LaTeXML-picture-xhtml.xsl index 1c5400006..45dfda7a0 100644 --- a/lib/LaTeXML/resources/XSLT/LaTeXML-picture-xhtml.xsl +++ b/lib/LaTeXML/resources/XSLT/LaTeXML-picture-xhtml.xsl @@ -169,8 +169,9 @@ ltx_foreignobject_container ltx_foreignobject_content - - + + + From 7ef75c214f5ba5d274628efd4628747c497ece80 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 7 May 2026 16:26:01 -0400 Subject: [PATCH 16/31] Define methods allowing \multicolumn to REPLACE the column spec, so it gets all properties --- lib/LaTeXML/Core/Alignment.pm | 12 ++++++++++-- lib/LaTeXML/Core/Alignment/Template.pm | 9 +++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/LaTeXML/Core/Alignment.pm b/lib/LaTeXML/Core/Alignment.pm index aeccb97d6..830200e51 100644 --- a/lib/LaTeXML/Core/Alignment.pm +++ b/lib/LaTeXML/Core/Alignment.pm @@ -21,8 +21,8 @@ use LaTeXML::Common::XML; use LaTeXML::Common::Dimension; use LaTeXML::Core::Alignment::Template; use List::Util qw(max sum); -use base qw(LaTeXML::Core::Whatsit); -use base qw(Exporter); +use base qw(LaTeXML::Core::Whatsit); +use base qw(Exporter); our @EXPORT = (qw( &ReadAlignmentTemplate &MatrixTemplate)); @@ -165,6 +165,14 @@ sub currentColumn { my ($self) = @_; return $$self{current_row} && $$self{current_row}->column($$self{current_column}); } +# Used by \multicolumn to replace the current column spec with a new one +# (the row has been cloned from the overall spec) +sub replaceColumn { + my ($self, $colspec) = @_; + $$self{current_row}->replaceColumn($$self{current_column}, $colspec) + if $$self{current_row}; + return; } + sub getColumn { my ($self, $n) = @_; return $$self{current_row} && $$self{current_row}->column($n); } diff --git a/lib/LaTeXML/Core/Alignment/Template.pm b/lib/LaTeXML/Core/Alignment/Template.pm index 16cd7d889..34caa900f 100644 --- a/lib/LaTeXML/Core/Alignment/Template.pm +++ b/lib/LaTeXML/Core/Alignment/Template.pm @@ -150,6 +150,15 @@ sub column { push(@{ $$self{columns} }, {%dup}); } } } return ($n > 0 ? $$self{columns}->[$n - 1] : undef); } +# Used by \multicolumn to replace column $n spec with a new one +# (the row has been cloned from the overall spec) +sub replaceColumn { + my ($self, $n, $colspec) = @_; + my $N = scalar(@{ $$self{columns} }); + if ($n <= $N) { + $$self{columns}->[$n] = $colspec; } + return; } + sub columns { my ($self) = @_; return @{ $$self{columns} }; } From 8fb9c8b07e0fe81d847844952e347efd4b269268 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 7 May 2026 16:34:13 -0400 Subject: [PATCH 17/31] FIx column specifiers p,m,b to use \vbox so size computed correctly; Make \multicolumn copy the new spec into the alignment, so properties preserved; when storing column data get data from correct column when it was spanned --- lib/LaTeXML/Engine/TeX_Tables.pool.ltxml | 59 +++++++++++++++--------- lib/LaTeXML/Package/array.sty.ltxml | 22 +++++---- 2 files changed, 51 insertions(+), 30 deletions(-) diff --git a/lib/LaTeXML/Engine/TeX_Tables.pool.ltxml b/lib/LaTeXML/Engine/TeX_Tables.pool.ltxml index f97fa9402..9197571f1 100644 --- a/lib/LaTeXML/Engine/TeX_Tables.pool.ltxml +++ b/lib/LaTeXML/Engine/TeX_Tables.pool.ltxml @@ -66,12 +66,11 @@ DefColumnType('r', sub { DefColumnType('p{Dimension}', sub { $LaTeXML::BUILD_TEMPLATE->addColumn( - before => Tokens(T_CS('\vtop'), T_BEGIN, T_CS('\hbox'), - T_LETTER('t'), T_LETTER('o'), $_[1]->revert, T_CS('\relax'), - T_BEGIN), + before => Tokens(T_CS('\vtop'), T_BEGIN, T_BEGIN, + T_CS('\hsize'), $_[1]->revert, T_CS('\relax')), after => Tokens(T_END, T_END), vattach => 'top', - align => 'justify', + align => 'justify', width => $_[1], ); return; }); DefColumnType('*{Number}{}', sub { @@ -170,7 +169,7 @@ DefConstructor('\halign BoxSpecification', beforeConstruct => sub { my ($document) = @_; $document->maybeCloseElement('ltx:p'); }, - afterDigest => sub { # After spec, but before body! + afterDigest => sub { # After spec, but before body! my ($stomach, $whatsit) = @_; $whatsit->setProperty(mode => 'internal_vertical'); $stomach->beginMode('restricted_horizontal'); @@ -360,7 +359,7 @@ sub digestAlignmentColumn { my ($stomach, $alignment, $lastwascr) = @_; my $gullet = $stomach->getGullet; my $ismath = $STATE->lookupValue('IN_MATH'); - my $mode = $STATE->lookupValue('MODE'); + my $mode = $STATE->lookupValue('MODE'); local @LaTeXML::LIST = (); # Scan for leading \omit, skipping over (& saving) \hline. Debug("Halign $alignment: COLUMN starting scan ($mode)") if $LaTeXML::DEBUG{halign}; @@ -456,16 +455,19 @@ sub trimColumnTemplate { # Given the boxes for an alignment cell, # extract & remove the various fills and rules from the ends to annotate the cell structure +# Note: With spanned columns (\multicolumn) \omit has moved to the LAST column of span, +# but we want to store data in the FIRST column, to be consistent with rowspan. sub extractAlignmentColumn { my ($alignment, $boxes) = @_; return () unless $alignment; # ?? # Note: $n0,$n1 is a VERY round-about way of tracking the column spanning! - my $ismath = $STATE->lookupValue('IN_MATH'); - my $n0 = (LookupValue('alignmentStartColumn') || 0) + 1; - my $n1 = $alignment->currentColumnNumber; - my $colspec = $alignment->getColumn($n0); - my $align = $$colspec{align} || 'left'; - my $border = ''; + my $ismath = $STATE->lookupValue('IN_MATH'); + my $n0 = (LookupValue('alignmentStartColumn') || 0) + 1; + my $n1 = $alignment->currentColumnNumber; + my $colspec0 = $alignment->getColumn($n0); # Where we'll PUT the info + my $colspec1 = $alignment->getColumn($n1); # But this is the spec USED + my $align = $$colspec1{align} || 'left'; + my $border = ''; # Peel off any boxes from both sides until we get the "meat" of the column. # from this we can establish borders, alignment and emptiness. # But we, of course, immediately put them back... @@ -473,7 +475,7 @@ sub extractAlignmentColumn { my @saveleft = (); my @saveright = (); my (@lspaces, @rspaces); - if (my $skip = $$colspec{tabskip}) { + if (my $skip = $$colspec1{tabskip}) { push(@lspaces, Digest(Tokens(T_CS('\hskip'), $skip->revert, T_CS('\relax')))); } while (@boxes) { if (ref $boxes[0] eq 'LaTeXML::Core::List') { @@ -515,24 +517,26 @@ sub extractAlignmentColumn { unshift(@saveright, pop(@boxes)); } else { last; } } - delete $$colspec{width} unless $align eq 'justify'; + delete $$colspec0{width} unless $align eq 'justify'; # Replacing boxes with the fil padding & vertical rules stripped off @boxes = (@saveleft, @boxes, @saveright); $boxes = List(@boxes, mode => $boxes->getProperty('mode')); # record relevant info in the Alignment. - $$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; + $$colspec0{align} = $align; + $$colspec0{width} = $$colspec1{width}; + $$colspec0{border} = $border = ($$colspec1{border} || '') . $border; + $$colspec0{boxes} = $boxes; + $$colspec0{lspaces} = List(@lspaces) if @lspaces; + $$colspec0{rspaces} = List(@rspaces) if @rspaces; + $$colspec0{colspan} = $n1 - $n0 + 1; if ($$alignment{in_tabular_head} || $$alignment{in_tabular_foot}) { - $$colspec{thead}{column} = 1; } + $$colspec0{thead}{column} = 1; } + for (my $i = $n0 + 1 ; $i <= $n1 ; $i++) { my $c = $alignment->getColumn($i); $$c{skipped} = 1 if $c; } - Debug("Halign $alignment: INSTALL column " . join(',', map { $_ . "=" . ToString($$colspec{$_}); } sort keys %$colspec)) if $LaTeXML::DEBUG{halign}; + Debug("Halign $alignment: INSTALL column " . join(',', map { $_ . "=" . ToString($$colspec0{$_}); } sort keys %$colspec0)) if $LaTeXML::DEBUG{halign}; return $boxes; } #====================================================================== @@ -691,6 +695,17 @@ DefMacro('\lx@alignment@multicolumn {Number} AlignmentTemplate {}', sub { $tokens->unlist, ($column ? afterCellUnlist($$column{after}) : ())); }); +DefMacro('\lx@alignment@multicolumn {Number}{}{}', sub { + my ($gullet, $span, $template, $tokens) = @_; + $span = $span->valueOf; + # First part, like \multispan + (T_CS('\omit'), (map { (T_CS('\span'), T_CS('\omit')) } 1 .. $span - 1), + T_CS('\lx@alignment@altcolumn'), T_BEGIN, $template, T_END, $tokens); }); +DefMacro('\lx@alignment@altcolumn AlignmentTemplate', sub { + my ($gullet, $template) = @_; + if (my $alignment = LookupValue('Alignment')) { + $alignment->replaceColumn($template->column(1)); } }); + DefConditionalI('\if@in@lx@alignment', undef, sub { LookupValue('Alignment'); }); DefPrimitive('\lx@alignment@bindings AlignmentTemplate []', sub { diff --git a/lib/LaTeXML/Package/array.sty.ltxml b/lib/LaTeXML/Package/array.sty.ltxml index 47dec92c1..e95d6e9f9 100644 --- a/lib/LaTeXML/Package/array.sty.ltxml +++ b/lib/LaTeXML/Package/array.sty.ltxml @@ -24,18 +24,24 @@ DefMacroI('\lasthline', undef, '\hline'); DefColumnType('>{}', sub { $LaTeXML::BUILD_TEMPLATE->addBeforeColumn($_[1]->unlist); return; }); DefColumnType('<{}', sub { $LaTeXML::BUILD_TEMPLATE->addAfterColumn($_[1]->unlist); return; }); -# This is the same as p +# This is the same as p, but using \vtop # but really needs to specify vertical alignment as centered DefColumnType('m{Dimension}', sub { - $LaTeXML::BUILD_TEMPLATE->addColumn(before => Tokens(T_CS('\vtop'), T_BEGIN), #Tokens(T_BEGIN), - after => Tokens(T_END), - align => 'justify', width => $_[1], vattach => 'middle'); return; }); -# This is also the same as p + $LaTeXML::BUILD_TEMPLATE->addColumn( + before => Tokens(T_CS('\vbox'), T_BEGIN, T_BEGIN, + T_CS('\hsize'), $_[1]->revert, T_CS('\relax')), + after => Tokens(T_END, T_END), + align => 'justify', width => $_[1], + vattach => 'middle'); return; }); +# This is also the same as p, but SHOULD use \vbot (if there was one) # but really needs to specify vertical alignment as bottom DefColumnType('b{Dimension}', sub { - $LaTeXML::BUILD_TEMPLATE->addColumn(before => Tokens(T_CS('\vbox'), T_BEGIN), #Tokens(T_BEGIN), - after => Tokens(T_END), - align => 'justify', width => $_[1], vattach => 'bottom'); return; }); + $LaTeXML::BUILD_TEMPLATE->addColumn( + before => Tokens(T_CS('\vbox'), T_BEGIN, T_BEGIN, + T_CS('\hsize'), $_[1]->revert, T_CS('\relax')), + after => Tokens(T_END, T_END), + align => 'justify', width => $_[1], + vattach => 'bottom'); return; }); # Like @{}, but should NOT suppress intercolumn space DefColumnType('!{}', sub { From 4e6ba3f8604181ddf79c55701d016f75f1ab973d Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Wed, 13 May 2026 16:50:31 -0400 Subject: [PATCH 18/31] Redo p{},m{},b{} to create ltx:inline-block, so they work alongside @{}, and also process the contents with \hsize set for proper sizing --- lib/LaTeXML/Engine/TeX_Tables.pool.ltxml | 14 ++++++++++---- lib/LaTeXML/Package/array.sty.ltxml | 23 +++++++++-------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/lib/LaTeXML/Engine/TeX_Tables.pool.ltxml b/lib/LaTeXML/Engine/TeX_Tables.pool.ltxml index 9197571f1..ab924fe4b 100644 --- a/lib/LaTeXML/Engine/TeX_Tables.pool.ltxml +++ b/lib/LaTeXML/Engine/TeX_Tables.pool.ltxml @@ -64,14 +64,20 @@ DefColumnType('c', sub { DefColumnType('r', sub { $LaTeXML::BUILD_TEMPLATE->addColumn(before => Tokens(T_CS('\hfil'))); return; }); +# Note that p typesets as a paragraph of given width, +# BUT @{} might add other contents to the cell! So, we need an inline-block DefColumnType('p{Dimension}', sub { $LaTeXML::BUILD_TEMPLATE->addColumn( - before => Tokens(T_CS('\vtop'), T_BEGIN, T_BEGIN, - T_CS('\hsize'), $_[1]->revert, T_CS('\relax')), - after => Tokens(T_END, T_END), + before => Tokens(T_CS('\lx@tabular@p'), T_LETTER('t'), T_BEGIN, $_[1]->revert, T_END, T_BEGIN), + after => Tokens(T_END), vattach => 'top', - align => 'justify', width => $_[1], ); return; }); +# Make sure the content sees the desired width as \hsize +DefMacro('\lx@tabular@p{}{}', '\hsize=#2\relax\lx@tabular@p@{#1}{#2}'); +DefConstructor('\lx@tabular@p@{}{Dimension} VBoxContents', + "?#3(#3)()", + sizer => '#3', reversion => '#3', + properties => sub { (vattach => translateAttachment($_[1])); }); DefColumnType('*{Number}{}', sub { my ($gullet, $n, $pattern) = @_; diff --git a/lib/LaTeXML/Package/array.sty.ltxml b/lib/LaTeXML/Package/array.sty.ltxml index e95d6e9f9..ec26b51be 100644 --- a/lib/LaTeXML/Package/array.sty.ltxml +++ b/lib/LaTeXML/Package/array.sty.ltxml @@ -24,24 +24,19 @@ DefMacroI('\lasthline', undef, '\hline'); DefColumnType('>{}', sub { $LaTeXML::BUILD_TEMPLATE->addBeforeColumn($_[1]->unlist); return; }); DefColumnType('<{}', sub { $LaTeXML::BUILD_TEMPLATE->addAfterColumn($_[1]->unlist); return; }); -# This is the same as p, but using \vtop -# but really needs to specify vertical alignment as centered +# m{} and b{} are like p{}, but vertical alignment is middle or bottom DefColumnType('m{Dimension}', sub { $LaTeXML::BUILD_TEMPLATE->addColumn( - before => Tokens(T_CS('\vbox'), T_BEGIN, T_BEGIN, - T_CS('\hsize'), $_[1]->revert, T_CS('\relax')), - after => Tokens(T_END, T_END), - align => 'justify', width => $_[1], - vattach => 'middle'); return; }); -# This is also the same as p, but SHOULD use \vbot (if there was one) -# but really needs to specify vertical alignment as bottom + before => Tokens(T_CS('\lx@tabular@p'), T_LETTER('m'), T_BEGIN, $_[1]->revert, T_END, T_BEGIN), + after => Tokens(T_END), + vattach => 'middle', + ); return; }); DefColumnType('b{Dimension}', sub { $LaTeXML::BUILD_TEMPLATE->addColumn( - before => Tokens(T_CS('\vbox'), T_BEGIN, T_BEGIN, - T_CS('\hsize'), $_[1]->revert, T_CS('\relax')), - after => Tokens(T_END, T_END), - align => 'justify', width => $_[1], - vattach => 'bottom'); return; }); + before => Tokens(T_CS('\lx@tabular@p'), T_LETTER('b'), T_BEGIN, $_[1]->revert, T_END, T_BEGIN), + after => Tokens(T_END), + vattach => 'bottom', + ); return; }); # Like @{}, but should NOT suppress intercolumn space DefColumnType('!{}', sub { From dda21baaf3949bb804bb9bc0aa7164e4fa253618 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Wed, 13 May 2026 16:51:16 -0400 Subject: [PATCH 19/31] Update several test cases affected by the different tabular p{} processing --- t/alignment/array.xml | 16 +++--- t/alignment/cells.xml | 24 ++++---- t/alignment/colortbls.xml | 72 ++++++++++++------------ t/alignment/halignatt.xml | 2 +- t/alignment/tabular.xml | 40 +++++++------- t/babel/numprints.xml | 10 ++-- t/graphics/graphrot.xml | 100 +++++++++++++++++++++------------- t/math/array_newline_math.xml | 2 +- 8 files changed, 146 insertions(+), 120 deletions(-) diff --git a/t/alignment/array.xml b/t/alignment/array.xml index be6d249e3..4096fbc79 100644 --- a/t/alignment/array.xml +++ b/t/alignment/array.xml @@ -19,24 +19,24 @@ - - Lorem ipsum dolor sit amet, consectetur adipisicing elit + + Lorem ipsum dolor sit amet, consectetur adipisicing elit - + Lorem ipsum dolor sit amet, consectetur adipisicing elit - + Lorem ipsum dolor sit amet, consectetur adipisicing elit - - Lorem ipsum dolor sit amet, consectetur adipisicing elit + + Lorem ipsum dolor sit amet, consectetur adipisicing elit - + Lorem ipsum dolor sit amet, consectetur adipisicing elit - + Lorem ipsum dolor sit amet, consectetur adipisicing elit diff --git a/t/alignment/cells.xml b/t/alignment/cells.xml index 54cb7453f..82b74577a 100644 --- a/t/alignment/cells.xml +++ b/t/alignment/cells.xml @@ -69,8 +69,8 @@ - - Cell long text with predefined width + + Cell long text with predefined width @@ -79,8 +79,8 @@ - - Cell long… + + Cell long… @@ -148,13 +148,13 @@ - - Second multilined + + Second multilined - - column head + + column head @@ -298,8 +298,8 @@ - - Cell … + + Cell … @@ -308,8 +308,8 @@ - - Cell … + + Cell … diff --git a/t/alignment/colortbls.xml b/t/alignment/colortbls.xml index 0ab5925dc..9a086e57b 100644 --- a/t/alignment/colortbls.xml +++ b/t/alignment/colortbls.xml @@ -95,11 +95,11 @@ p{3cm}}"?> - - P-column + + P-column - - and another one + + and another one @@ -108,8 +108,8 @@ p{3cm}}"?> - Total - (wrong) + Total + (wrong) 100⋅6 @@ -117,11 +117,11 @@ p{3cm}}"?> - - Some long text in the first column + + Some long text in the first column - - bbb + + bbb @@ -130,11 +130,11 @@ p{3cm}}"?> - - aaa + + aaa - - and some long text in the second column + + and some long text in the second column @@ -143,8 +143,8 @@ p{3cm}}"?> - Total - (wrong) + Total + (wrong) 100⋅6 @@ -152,11 +152,11 @@ p{3cm}}"?> - - aaa + + aaa - - bbb + + bbb @@ -165,12 +165,12 @@ p{3cm}}"?> - - Note that the coloured rules in all columns stretch to accomodate + + Note that the coloured rules in all columns stretch to accomodate large entries in one column. - - bbb + + bbb @@ -179,11 +179,11 @@ large entries in one column. - - aaa + + aaa - - bbb + + bbb @@ -192,11 +192,11 @@ large entries in one column. - - aaa + + aaa - - Depending on your driver you may get unsightly gaps or lines + + Depending on your driver you may get unsightly gaps or lines where the ‘screens’ used to produce different shapes interact badly. You may want to cause adjacent panels of the same colour by specifying a larger overhang @@ -209,11 +209,11 @@ or by adding some negative space (in a ”“noalign” between rows. - - aaa + + aaa - - bbb + + bbb diff --git a/t/alignment/halignatt.xml b/t/alignment/halignatt.xml index 41296325b..5e81f40cd 100644 --- a/t/alignment/halignatt.xml +++ b/t/alignment/halignatt.xml @@ -53,7 +53,7 @@ .95* - * (first quarter only) + * (first quarter only) diff --git a/t/alignment/tabular.xml b/t/alignment/tabular.xml index b7cd9df21..5bcfd6edf 100644 --- a/t/alignment/tabular.xml +++ b/t/alignment/tabular.xml @@ -13,13 +13,13 @@ Price - + Year low high - Comments + Comments @@ -27,24 +27,24 @@ 1971 97– 245 - - Bad year. + + Bad year. 72 245– 245 - - Light trading due to a heavy winter. + + Light trading due to a heavy winter. 73 245– 2001 - - No gnus was very good gnus this year. + + No gnus was very good gnus this year. @@ -77,25 +77,25 @@ - 1 - a + 1 + a 2 - - b + + b 3 - - c + + c 4 - 1 - a a a a a a a a a a + 1 + a a a a a a a a a a 2 - - b + + b 3 - - c + + c 4 diff --git a/t/babel/numprints.xml b/t/babel/numprints.xml index 2062b1fee..bb89e864a 100644 --- a/t/babel/numprints.xml +++ b/t/babel/numprints.xml @@ -811,15 +811,15 @@ vs. - without braces + without braces with braces with braces and box - with braces, exp, and box + with braces, exp, and box - error + error abc def @@ -901,7 +901,7 @@ vs. rt - mor + mor e45,1 @@ -963,7 +963,7 @@ vs. txt - notblu + notblu e45,1 diff --git a/t/graphics/graphrot.xml b/t/graphics/graphrot.xml index 9cb6f16ea..6ae8076ff 100644 --- a/t/graphics/graphrot.xml +++ b/t/graphics/graphrot.xml @@ -395,7 +395,7 @@ the whales Save the whale Save the + Table 5 Table 5 @@ -413,12 +413,12 @@ the whales Save the whale Save the Pottery Flint Animal - - Stone + + Stone Other - - C14 Dates + + C14 Dates @@ -430,15 +430,28 @@ the whales Save the whale Save the Bones - + - + - Grooved Ware - + + + + + + + + + + + + + + Grooved Ware + 784 @@ -457,14 +470,14 @@ the whales Save the whale Save the × 8 - + × 2 bone - - 2150 + + 2150 ± @@ -488,12 +501,12 @@ the whales Save the whale Save the × 21 - - Hammerstone + + Hammerstone — - - — + + — @@ -513,12 +526,12 @@ the whales Save the whale Save the × 57* - - — + + — — - - 1990 + + 1990 ± @@ -546,17 +559,30 @@ the whales Save the whale Save the × 8 - - — + + — Fired clay - - — + + — + + + + + + + + + + + + + Beaker - + 552 @@ -567,12 +593,12 @@ the whales Save the whale Save the P7–14 — — - - — + + — — - - — + + — @@ -588,12 +614,12 @@ the whales Save the whale Save the — - - Quartzite-lump + + Quartzite-lump — - - — + + — @@ -609,12 +635,12 @@ the whales Save the whale Save the — - - — + + — — - - — + + — diff --git a/t/math/array_newline_math.xml b/t/math/array_newline_math.xml index 71cc48525..6eb31fd14 100644 --- a/t/math/array_newline_math.xml +++ b/t/math/array_newline_math.xml @@ -13,7 +13,7 @@ 4 - + From 004f732cc1cd48992ecdecbfb7dc42e7aa0417f3 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Tue, 26 May 2026 12:20:41 -0400 Subject: [PATCH 20/31] Support passing more size releated properties to Font: pad(top|bottom|right|left); tweak debug feedback more informative, less noisy --- lib/LaTeXML/Core/Box.pm | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/LaTeXML/Core/Box.pm b/lib/LaTeXML/Core/Box.pm index 07216bfe6..87460186e 100644 --- a/lib/LaTeXML/Core/Box.pm +++ b/lib/LaTeXML/Core/Box.pm @@ -38,7 +38,7 @@ sub Box { $properties{height} = Dimension(0) unless defined $properties{height}; $properties{depth} = Dimension(0) unless defined $properties{depth}; } my $state = $STATE; - my $mode = $properties{mode} || $state->lookupValue('MODE') || 'restricted_horizontal'; + my $mode = $properties{mode} || $state->lookupValue('MODE') || 'restricted_horizontal'; if ($state->lookupValue('IN_MATH')) { my $attr = (defined $string) && $state->lookupValue('math_token_attributes_' . $string); my $usestring = ($attr && $$attr{replace}) || $string; @@ -47,7 +47,7 @@ sub Box { mode => $mode, ($attr ? %$attr : ()), %properties); } else { return LaTeXML::Core::Box->new($string, $font, $locator, $tokens, - mode => $mode, %properties); } } + mode => $mode, %properties); } } #====================================================================== # Box Object @@ -113,10 +113,10 @@ my %mode_abbrev = ( sub _stringify { my ($self) = @_; - my $type = ref $self; - my $mode = $$self{properties}{mode}; + my $type = ref $self; + my $mode = $$self{properties}{mode}; $type =~ s/^LaTeXML::Core:://; - $type .= '!'. ($mode_abbrev{$mode} || $mode) if $mode; + $type .= '!' . ($mode_abbrev{$mode} || $mode) if $mode; return $type; } sub stringify { @@ -235,12 +235,6 @@ sub getSize { unless (defined $$props{cwidth}) && (defined $$props{cheight}) && (defined $$props{cdepth}); - Debug("SIZE of $self" - . "\n preassigned: " . _showsize($$props{width}, $$props{height}, $$props{depth}) - . "\n calculated : " . _showsize($$props{cwidth}, $$props{cheight}, $$props{cdepth}) - . "\n w/options " . join(',', map { $_ . "=" . ToString($options{$_}); } sort keys %options) - . "\n =>: " . _showsize($$props{width} || $$props{cwidth}, $$props{height} || $$props{cheight}, $$props{depth} || $$props{cdepth}) - . "\n Of " . ToString($self)) if $LaTeXML::DEBUG{size}; return ($$props{width} || $$props{cwidth}, $$props{height} || $$props{cheight}, $$props{depth} || $$props{cdepth}, @@ -260,19 +254,25 @@ sub showSize { #omg # Fake computing the dimensions of strings (typically single chars). # Eventually, this needs to link into real font data +our @sizing_properties = (qw( + width height depth totalheight vattach layout + padtop padbottom padleft padright)); + sub computeSizeStore { my ($self, %options) = @_; no warnings 'recursion'; my $props = $self->getPropertiesRef; - $options{width} = $$props{width} if $$props{width}; - $options{height} = $$props{height} if $$props{height}; - $options{depth} = $$props{depth} if $$props{depth}; - $options{vattach} = $$props{vattach} if $$props{vattach}; - $options{layout} = $$props{layout} if $$props{layout}; + map { $options{$_} = $$props{$_} if defined $$props{$_}; } @sizing_properties; my ($w, $h, $d) = $self->computeSize(%options); $$props{cwidth} = $w unless defined $$props{cwidth}; $$props{cheight} = $h unless defined $$props{cheight}; $$props{cdepth} = $d unless defined $$props{cdepth}; + Debug("SIZE of $self" + . "\n preassigned: " . _showsize($$props{width}, $$props{height}, $$props{depth}) + . "\n calculated : " . _showsize($$props{cwidth}, $$props{cheight}, $$props{cdepth}) + . "\n w/options " . join(',', map { $_ . "=" . ToString($options{$_}); } sort keys %options) + . "\n =>: " . _showsize($$props{width} || $$props{cwidth}, $$props{height} || $$props{cheight}, $$props{depth} || $$props{cdepth}) + . "\n Of " . ToString($self)) if $LaTeXML::DEBUG{size}; return; } sub computeSize { From 2b2ed28987f5fd68acf34451562fef1860da1802 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Tue, 26 May 2026 12:24:22 -0400 Subject: [PATCH 21/31] Simplify simple underline/overline --- lib/LaTeXML/Engine/TeX_Math.pool.ltxml | 28 +++++++++----------------- lib/LaTeXML/resources/CSS/LaTeXML.css | 3 +++ t/fonts/marvosym.xml | 4 ++-- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/lib/LaTeXML/Engine/TeX_Math.pool.ltxml b/lib/LaTeXML/Engine/TeX_Math.pool.ltxml index 2f81ba7a4..b8bc3b680 100644 --- a/lib/LaTeXML/Engine/TeX_Math.pool.ltxml +++ b/lib/LaTeXML/Engine/TeX_Math.pool.ltxml @@ -300,7 +300,7 @@ sub cleanup_XMText { $document->replaceNode($m, map { $_->childNodes } $m->childNodes); } else { # Otherwise, wrap whatever it is in an XMText $document->wrapNodes('ltx:XMText', $m); } - } } } + } } } # And now we don't need the XMText any more. foreach my $attr ($textnode->attributes) { # Copy the child's attributes (should Merge!!) $table->setAttribute($attr->nodeName => $attr->getValue); } @@ -423,7 +423,7 @@ sub scriptHandler { my $c = (($op eq 'SUPERSCRIPT') ? '^' : '_'); Error('unexpected', $c, $stomach, "Script $c can only appear in math mode"); return Box($c, undef, undef, (($op eq 'SUPERSCRIPT') ? T_SUPER : T_SUB)); -} } + } } DefPrimitiveI(T_SUPER, undef, sub { scriptHandler($_[0], 'SUPERSCRIPT'); }); DefPrimitiveI(T_SUB, undef, sub { scriptHandler($_[0], 'SUBSCRIPT'); }); @@ -544,7 +544,7 @@ DefRewrite(xpath => 'descendant::ltx:Math[child::ltx:XMath[child::ltx:XMApp[' . elsif ($role eq 'FLOATSUBSCRIPT') { $document->insertElement('ltx:sub', $text); return; } - } } } } + } } } } # should never happen, but just in case: Info("rewrite", "footnotemark", "Failed to find floating node in: " . $math->toString(1)); $document->getNode->appendChild($math); @@ -819,7 +819,7 @@ sub augmentDelimiterProperties { if (exists $$entry{char}) { # replace the char content! if (my $char = $$entry{char}) { $delim->firstChild->setData($char); } } - } } + } } return; } # Useful for afterConstruct of delimiter sizing macros (eg. \bigl) @@ -952,24 +952,16 @@ DefMath('\lx@math@overline{}', UTF(0xAF), operator_role => 'OVERACCENT', operator_stretchy => 'true', name => 'overline', alias => '\overline'); DefConstructor('\lx@text@overline{}', - "#1", - enterHorizontal => 1, mode => 'restricted_horizontal', - sizer => sub { - my $text = $_[0]->getArg(1); - my ($w, $h, $d) = $text->getSize; - my $pad = Dimension($text->getFont->getEXHeight); # WAG - return ($w, $h->add($pad), $d); }); + "#1", + enterHorizontal => 1, mode => 'restricted_horizontal', + sizer => '#1', properties => { padtop => Dimension('2pt') }); DefMath('\lx@math@underline{}', UTF(0xAF), operator_role => 'UNDERACCENT', operator_stretchy => 'true', name => 'underline', alias => '\underline'); DefConstructor('\lx@text@underline{}', - "#1", - enterHorizontal => 1, mode => 'restricted_horizontal', - sizer => sub { - my $text = $_[0]->getArg(1); - my ($w, $h, $d) = $text->getSize; - my $pad = Dimension($text->getFont->getEXHeight); # WAG - return ($w, $h, $d->add($pad)); }); + "#1", + enterHorizontal => 1, mode => 'restricted_horizontal', + sizer => '#1', properties => { padbottom => Dimension('2pt') }); DefMath('\lx@math@overrightarrow{}', "\x{2192}", operator_role => 'OVERACCENT', operator_stretchy => 'true', name => 'overrightarrow', alias => '\overrightarrow'); diff --git a/lib/LaTeXML/resources/CSS/LaTeXML.css b/lib/LaTeXML/resources/CSS/LaTeXML.css index 4f4578337..758ff3d0c 100644 --- a/lib/LaTeXML/resources/CSS/LaTeXML.css +++ b/lib/LaTeXML/resources/CSS/LaTeXML.css @@ -442,6 +442,9 @@ span.ltx_framed { display:inline-block; text-indent:0; } /* avoid padding/ .ltx_rule { vertical-align: bottom; height: 0.4pt; width: 0.4pt; } +.ltx_underline { text-decoration: underline; } +.ltx_overline { text-decoration: overline; } + /*====================================================================== Misc */ /* .ltx_verbatim*/ diff --git a/t/fonts/marvosym.xml b/t/fonts/marvosym.xml index d902bc3ac..b0f38ec92 100644 --- a/t/fonts/marvosym.xml +++ b/t/fonts/marvosym.xml @@ -126,9 +126,9 @@ △╳, Ⓐ, Ⓟ, -Ⓟ, +Ⓟ, Ⓕ, -Ⓕ, +Ⓕ, \Ironing, \ironing, \IRONING, From ab6fe78bb6f4bb46cfa4c584aa548f5fbc4c84df Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Tue, 26 May 2026 12:25:24 -0400 Subject: [PATCH 22/31] Update deprecated --- lib/LaTeXML/Package/fancyvrb.sty.ltxml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/LaTeXML/Package/fancyvrb.sty.ltxml b/lib/LaTeXML/Package/fancyvrb.sty.ltxml index 69e3afdac..caee3477f 100644 --- a/lib/LaTeXML/Package/fancyvrb.sty.ltxml +++ b/lib/LaTeXML/Package/fancyvrb.sty.ltxml @@ -22,7 +22,7 @@ InputDefinitions('fancyvrb', type => 'sty', noltxml => 1); # Hack internals to add css class ltx_nowrap Let('\lx@save@FancyVerbFormatLine', '\FancyVerbFormatLine'); DefMacro('\FancyVerbFormatLine{}', - '\@ADDCLASS{ltx_verbatim}\lx@save@FancyVerbFormatLine{#1}'); + '\lx@add@cssclass{ltx_verbatim}\lx@save@FancyVerbFormatLine{#1}'); #********************************************************************** 1; From 55458d0beb6517ccc858bfbc180fe91a0b4f7314 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Tue, 26 May 2026 12:27:24 -0400 Subject: [PATCH 23/31] Add padding to items, equations --- lib/LaTeXML/Engine/TeX_Math.pool.ltxml | 5 +++- .../Engine/latex_constructs.pool.ltxml | 27 ++++++++++++------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/lib/LaTeXML/Engine/TeX_Math.pool.ltxml b/lib/LaTeXML/Engine/TeX_Math.pool.ltxml index b8bc3b680..fe64321c2 100644 --- a/lib/LaTeXML/Engine/TeX_Math.pool.ltxml +++ b/lib/LaTeXML/Engine/TeX_Math.pool.ltxml @@ -133,7 +133,10 @@ DefConstructorI('\lx@begin@display@math', undef, beforeDigest => sub { $_[0]->enterHorizontal; $_[0]->beginMode('display_math'); }, - properties => { mode => 'display_math' }, + properties => sub { + (mode => 'display_math', + padtop => LookupRegister('\abovedisplayskip'), + padbottom => LookupRegister('\belowdisplayskip')); }, captureBody => 1); DefConstructorI('\lx@end@display@math', undef, "", reversion => Tokens(T_MATH, T_MATH), diff --git a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml index 3f57de830..d9b8df62a 100644 --- a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml +++ b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml @@ -1368,7 +1368,11 @@ sub RefStepItemCounter { my %attr = (); my $sep = LookupDimension('\itemsep'); if (($n > 0) && $sep && ($sep->valueOf != LookupDimension('\lx@default@itemsep')->valueOf)) { - $attr{itemsep} = $sep; } + $attr{itemsep} = $sep; + # this SHOULD pad the item, but \item is ONLY the marker, not the item content! +## $attr{padtop} = $sep; +## $attr{height} = Dimension('1em'); + } if (defined $tag) { my @props = RefStepID($counter); if (IsEmpty($tag)) { # empty tag? @@ -1670,11 +1674,11 @@ DefConstructor('\trivlist@item@ OptionalUndigested', properties => sub { ($_[1] ? (tag => Digest(Expand($_[1]))) : ()); }); DefMacro('\@trivlist', '\relax', locked => 1); -DefRegister('\topsep' => Glue(0)); -DefRegister('\partopsep' => Glue(0)); -DefRegister('\lx@default@itemsep' => Glue(0)); -DefRegister('\itemsep' => Glue(0)); -DefRegister('\parsep' => Glue(0)); +DefRegister('\topsep' => Glue('8pt plus 2pt minus 4pt')); +DefRegister('\partopsep' => Glue('2pt plus 1pt minus 1pt')); +DefRegister('\lx@default@itemsep' => Glue('4pt plus 2pt minus 1pt')); +DefRegister('\itemsep' => Glue('4pt plus 2pt minus 1pt')); +DefRegister('\parsep' => Glue('4pt plus 2pt minus 1pt')); DefRegister('\@topsep' => Glue(0)); DefRegister('\@topsepadd' => Glue(0)); DefRegister('\@outerparskip' => Glue(0)); @@ -2024,6 +2028,9 @@ sub afterEquation { elsif ($whatsit) { $whatsit->setProperties(%{ LookupValue('EQUATIONROW_TAGS') }); } $$numbering{in_equation} = 0; + $whatsit->setProperties( + padtop => LookupRegister('\abovedisplayskip'), + padbottom => LookupRegister('\belowdisplayskip')) if $whatsit; return; } # My first inclination is to Lock {math}, but it is surprisingly common to redefine it in silly ways... So...? @@ -2969,7 +2976,7 @@ sub defineNewTheorem { T_CS('\let'), T_CS('\the' . $counter), T_CS('\@empty'), Invocation(T_CS('\lx@make@tags'), T_OTHER($thmset)), T_END); - } } + } } else { %ctr = RefStepCounter($thmset); } } my $title = DigestText(Tokens(T_BEGIN, @@ -3317,7 +3324,7 @@ sub arrange_panels_and_breaks { my $block = $document->wrapNodes('ltx:block', $prev_node, $child); push(@row_contents, [$block, 'ltx:block', $prev_width + $child_width]); pop(@all_panels); push(@all_panels, $block); - } } + } } else { # otherwise keep the last panel as-is, and append a sibling push(@row_contents, $prev_panel); @@ -4762,8 +4769,8 @@ DefConstructor('\lx@parbox[][Dimension] OptionalUndigested {Dimension} VBoxConte sizer => '#5', properties => sub { (width => $_[4], - vattach => translateAttachment($_[1]), - height => $_[2]); }, + vattach => translateAttachment($_[1]), + totalheight => $_[2]); }, mode => 'inline_internal_vertical', robust => 1, beforeDigest => sub { From e654a134d03a00609793fc05cef73560b4be6c7b Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Tue, 26 May 2026 12:29:06 -0400 Subject: [PATCH 24/31] Let space squeeze when laying out paragraphs; more informative debugging; support properties for padding, totalheight --- lib/LaTeXML/Common/Font.pm | 58 ++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/lib/LaTeXML/Common/Font.pm b/lib/LaTeXML/Common/Font.pm index ea1855d6e..d9a3c7ba2 100644 --- a/lib/LaTeXML/Common/Font.pm +++ b/lib/LaTeXML/Common/Font.pm @@ -646,6 +646,7 @@ sub computeBoxesSize { || $STATE->lookupDefinition(T_CS('\hsize')); $wrapwidth = $wrapwidth->valueOf if ref $wrapwidth; # Register or Dimension $wrapwidth = $wrapwidth->valueOf if ref $wrapwidth; } # still Dimension (Register) + my $maxwidth = $wrapwidth || 0; no warnings 'recursion'; my @boxes = grep { !(ref $_) || !$_->getProperty('isEmpty') } grep { !(ref $_) || $_->can('getSize'); } $boxes->unlist; @@ -659,6 +660,7 @@ sub computeBoxesSize { && (($box->getProperty('mode') || '') eq 'horizontal')) { my $width = $box->getProperty('width') || $wrapwidth; $width = $width->valueOf if ref $width; + $maxwidth = $width if $width && $width > $maxwidth; push(@lines, $self->computeBoxesSize_lines($width, $self->computeBoxesSize_words($box->unlist))); } else { @@ -666,17 +668,30 @@ sub computeBoxesSize { push(@lines, [$w, $h, $d]) if $w || $h || $d; } } } else { # Scan all boxes, collecting into "words", then (possibly) break into lines. + # Should get single line, if no $wrapwidth my @words = $self->computeBoxesSize_words(@boxes); @lines = $self->computeBoxesSize_lines($wrapwidth, @words); } # ---------------------------------------------------------------------- # Now, stack up the multiple lines my ($wd, $ht, $dp) = $self->computeBoxesSize_stack($vattach, @lines); - + $wd = $maxwidth if $wd && $maxwidth; # Set to wrapwidth, unless empty. + if (my $th = $options{totalheight}) { # divie up totalheight, if requested + $th = $th->valueOf; + my $diff = $th - $ht - $dp; + if ($diff > 0) { + if ($vattach eq 'bottom') { $ht += $diff; } + elsif ($vattach eq 'middle') { $ht += $diff/2; $dp += $diff/2; } + else { $dp += $diff; } } } Debug("Size boxes " . join(',', map { $_ . '=' . ToString($options{$_}); } sort keys %options) . "\n" . " Boxes: " . ToString($boxes) . "\n" . " Boxes: " . Stringify($boxes) . "\n" + . " Options:" . join(',',map { $_."=".ToString($options{$_}); } sort keys %options) . " Sizes: " . join("\n", map { _showsize(@$_); } @lines) . "\n" . " => " . _showsize($wd, $ht, $dp)) if $LaTeXML::DEBUG{'size-detailed'}; + $wd += $options{padleft}->valueOf if $options{padleft}; + $wd += $options{padright}->valueOf if $options{padright}; + $ht += $options{padtop}->valueOf if $options{padtop}; + $dp += $options{padbottom}->valueOf if $options{padbottom}; return (Dimension($wd), Dimension($ht), Dimension($dp)); } # Compute (w/guards) the size of a single box @@ -706,6 +721,7 @@ sub computeBoxesSize_words { no warnings 'recursion'; my ($self, @boxes) = @_; my @words = (); + my @word = (); my $prevbox; my $prevspace = 0; my $size = int($self->getSize || DEFSIZE() || 10); @@ -715,21 +731,22 @@ sub computeBoxesSize_words { # Check for possible line-break points if ((ref $box) && $box->getProperty('isBreak')) { if ($wd || $ht || $dp || ($prevspace > 0)) { - push(@words, [$prevspace, $wd, $ht, $dp]); - $wd = $ht = $dp = 0; $prevspace = -1; } + push(@words, [$prevspace, $wd, $ht, $dp, @word]); + $wd = $ht = $dp = 0; $prevspace = -1; @word = (); } else { $prevspace = -1; } } # Pernaps not "isSpace", but excluding struts, neg space, etc ??? elsif ((ref $box) && $box->getProperty('isSpace') && !$box->getProperty('isVerticalSpace')) { if ($wd || $ht || $dp || ($prevspace < 0)) { - push(@words, [$prevspace, $wd, $ht, $dp]); - $wd = $ht = $dp = 0; $prevspace = $w; } + push(@words, [$prevspace, $wd, $ht, $dp, @word]); + $wd = $ht = $dp = 0; $prevspace = $w; @word = (); } else { $prevspace += $w; } } else { # Else accumulate into "word" $wd += $w; $ht = max($ht, $h); $dp = max($dp, $d); + push(@word, $box); # Kern HACK for lists of individual Box's if ($prevbox && (ref $prevbox eq 'LaTeXML::Core::Box') && (ref $box eq 'LaTeXML::Core::Box')) { my $prevchar = substr($prevbox->getString || '', -1, 1); @@ -741,28 +758,32 @@ sub computeBoxesSize_words { $wd += $size * $kern; } } } $prevbox = $box; } - if ($wd || $ht || $dp || $prevspace) { # be sure to get last bit - push(@words, [$prevspace, $wd, $ht, $dp]); } + if ($wd || $ht || $dp || $prevspace || @word) { # be sure to get last bit + push(@words, [$prevspace, $wd, $ht, $dp, @word]); } return @words; } # do line breaking of words into lines, according to $wrapwidth (if), or explicit breaks. sub computeBoxesSize_lines { my ($self, $wrapwidth, @words) = @_; my @lines = (); + my @line = (); + my $fuzz = Dimension('1pt')->valueOf; + my $squeeze = ($wrapwidth ? 0.6 : 1.0); # Let spaces shrink in paragraph mode my ($wd, $ht, $dp) = (0, 0, 0); foreach my $item (@words) { - my ($space, $w, $h, $d) = @$item; + my ($space, $w, $h, $d, @word) = @$item; if ($space == -1) { - push(@lines, [$wd, $ht, $dp]) if $wd; - $wd = $w; $ht = $h; $dp = $d; } - elsif ((defined $wrapwidth) && ($wd + $space * 0.5 + $w > $wrapwidth)) { - push(@lines, [$wrapwidth || $wd, $ht, $dp]) if $wd; - $wd = $w; $ht = $h; $dp = $d; } + push(@lines, [$wd, $ht, $dp, @line]) if $wd; + $wd = $w; $ht = $h; $dp = $d; @line = @word; } + elsif ((defined $wrapwidth) && ($wd + $space * 0.5 + $w > $wrapwidth + $fuzz)) { + push(@lines, [$wd, $ht, $dp, @line]) if $wd; + $wd = $w; $ht = $h; $dp = $d; @line = @word; } else { - $wd += $space + $w; + $wd += $space*$squeeze + $w; $ht = max($ht, $h); - $dp = max($dp, $d); } } - push(@lines, [$wrapwidth || $wd, $ht, $dp]) if $wd || $ht || $dp; + $dp = max($dp, $d); + push(@line, @word); } } + push(@lines, [$wd, $ht, $dp, @line]) if $wd || $ht || $dp; return @lines; } # Sum up a stack of lines, determining w as max, and h & d according to $vattach. @@ -835,8 +856,9 @@ sub math_bearing { return $STATE->lookupDefinition($$mathbearingreg[abs($bearing)])->valueOf->spValue; } sub _showsize { - my ($wd, $ht, $dp) = @_; - return ($wd / $UNITY) . " x " . ($ht / $UNITY) . " + " . ($dp / $UNITY); } + my ($wd, $ht, $dp, @stuff) = @_; + return ($wd / $UNITY) . " x " . ($ht / $UNITY) . " + " . ($dp / $UNITY) + . (@stuff ? ' '.join('', map { ToString($_); } @stuff) : ''); } sub isSticky { my ($self) = @_; From f6309c6f8db37633472cd322967350899cfc2d88 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 28 May 2026 17:22:49 -0400 Subject: [PATCH 25/31] Bypass size computation if Box size completely specified --- lib/LaTeXML/Core/Box.pm | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/LaTeXML/Core/Box.pm b/lib/LaTeXML/Core/Box.pm index 87460186e..87087c629 100644 --- a/lib/LaTeXML/Core/Box.pm +++ b/lib/LaTeXML/Core/Box.pm @@ -263,7 +263,15 @@ sub computeSizeStore { no warnings 'recursion'; my $props = $self->getPropertiesRef; map { $options{$_} = $$props{$_} if defined $$props{$_}; } @sizing_properties; - my ($w, $h, $d) = $self->computeSize(%options); + my $w = $options{width}; + my $h = $options{height}; + my $d = $options{depth}; + if ((defined $w) && (defined $h) && (defined $d)) { + $w = Dimension($w) unless ref $w; + $h = Dimension($h) unless ref $h; + $d = Dimension($d) unless ref $d; } + else { + ($w, $h, $d) = $self->computeSize(%options); } $$props{cwidth} = $w unless defined $$props{cwidth}; $$props{cheight} = $h unless defined $$props{cheight}; $$props{cdepth} = $d unless defined $$props{cdepth}; From c56d7ec189b619ac3423a06a75b49acc3392f02c Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 28 May 2026 17:28:02 -0400 Subject: [PATCH 26/31] Make \vskip specified as pure height, no width, depth --- lib/LaTeXML/Engine/TeX_Glue.pool.ltxml | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/LaTeXML/Engine/TeX_Glue.pool.ltxml b/lib/LaTeXML/Engine/TeX_Glue.pool.ltxml index 50295cbf0..99a4962d2 100644 --- a/lib/LaTeXML/Engine/TeX_Glue.pool.ltxml +++ b/lib/LaTeXML/Engine/TeX_Glue.pool.ltxml @@ -84,21 +84,22 @@ DefConstructor('\hskip Glue', sub { reversion => sub { revertSkip(T_CS('\hskip'), $_[1]); }, attributeForm => sub { DimensionToSpaces($_[1]); }, enterHorizontal => 1, - properties => sub { (width => $_[1], isSpace => 1, isSkip => 1); }); + properties => sub { (width => $_[1], isSpace => 1, isSkip => 1); }); # We should be combining adjacent vskips (?) # And then add appropriate class/css for spacing to preceding element. DefConstructor('\vskip Glue', sub { - my ($document, $length, %props) = @_; - $length = $length->ptValue; - if ($length <= 0) { } # Or what!?!?!?! - elsif(($length < 4) && $document->isCloseable('ltx:p')) { - $document->closeElement('ltx:p'); } - elsif($document->isCloseable('ltx:para')) { - $document->closeElement('ltx:para'); } - return; }, + my ($document, $length, %props) = @_; + $length = $length->ptValue; + if ($length <= 0) { } # Or what!?!?!?! + elsif (($length < 4) && $document->isCloseable('ltx:p')) { + $document->closeElement('ltx:p'); } + elsif ($document->isCloseable('ltx:para')) { + $document->closeElement('ltx:para'); } + return; }, leaveHorizontal => 1, - properties => sub { (height => $_[1], isSpace => 1, isSkip => 1, isVerticalSpace => 1); }); + properties => sub { (height => $_[1], width => Dimension(0), depth => Dimension(0), + isSpace => 1, isSkip => 1, isVerticalSpace => 1); }); # Remove skip, if last on LIST DefPrimitiveI('\unskip', undef, sub { @@ -125,12 +126,12 @@ DefPrimitiveI('\hss', undef, undef, enterHorizontal => 1,); DefPrimitiveI('\hfilneg', undef, undef, enterHorizontal => 1); DefPrimitiveI('\hfil', undef, sub { - Box(' ', undef, undef, T_CS('\hfil'), isSpace => 1, isFill => 1); }, + Box(' ', undef, undef, T_CS('\hfil'), isSpace => 1, isFill => 1); }, enterHorizontal => 1); ### Box("\x{200B}", undef, undef, T_CS('\hfil'), isSpace => 1, isFill => 1); }); ### Box("\x{200A}", undef, undef, T_CS('\hfil'), isSpace => 1, isFill => 1); }); DefPrimitiveI('\hfill', undef, sub { - Box(' ', undef, undef, T_CS('\hfill'), isSpace => 1, isFill => 1); }, + Box(' ', undef, undef, T_CS('\hfill'), isSpace => 1, isFill => 1); }, enterHorizontal => 1); #### Box("\x{200B}", undef, undef, T_CS('\hfill'), isSpace => 1, isFill => 1); }); ### Box("\x{200A}", undef, undef, T_CS('\hfill'), isSpace => 1, isFill => 1); }); From 86cc33a531c2ad870767859190202717fdb538b2 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 28 May 2026 17:31:44 -0400 Subject: [PATCH 27/31] reindent graphics.sty --- lib/LaTeXML/Package/graphics.sty.ltxml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/LaTeXML/Package/graphics.sty.ltxml b/lib/LaTeXML/Package/graphics.sty.ltxml index f3878e1ea..45cb60871 100644 --- a/lib/LaTeXML/Package/graphics.sty.ltxml +++ b/lib/LaTeXML/Package/graphics.sty.ltxml @@ -63,9 +63,9 @@ DefParameterType('GraphixDimensions', sub { sub graphics_scaledbox_props { my ($box, $xscale, $yscale) = @_; my ($w, $h, $d) = $box->getSize; - my ($sw, $sh, $sd) = + my ($sw, $sh, $sd) = ($w && $w->multiply($xscale), $h && $h->multiply($yscale), $d && $d->multiply($yscale)); - my $H = $h && ($d ? $h->add($d) : $h); + my $H = $h && ($d ? $h->add($d) : $h); my $sH = $sh && ($sd ? $sh->add($sd) : $sh); return ( box => $box, @@ -78,7 +78,7 @@ sub graphics_scaledbox_props { depth => $sd, totalheight => $h && ($d ? $h->add($d) : $h)->multiply($yscale), xtranslate => $sw && $w && $sw->subtract($w)->multiply(+0.5), - ytranslate => $sH && $H && $sH->subtract($H)->multiply(-0.5) ); } + ytranslate => $sH && $H && $sH->subtract($H)->multiply(-0.5)); } sub graphics_scaledbox_insert { my ($document, %props) = @_; From 7229b26095a649d8bc97e7507e524299c378fe02 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 28 May 2026 17:49:12 -0400 Subject: [PATCH 28/31] parbox doesn't indent paragraphs --- lib/LaTeXML/Engine/latex_constructs.pool.ltxml | 4 +++- lib/LaTeXML/resources/CSS/LaTeXML.css | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml index d9b8df62a..5df0b57ad 100644 --- a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml +++ b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml @@ -4756,7 +4756,9 @@ Let('\lx@parboxnewline', '\lx@newline'); # Obsolete, but in case still used # NOTE: There are 2 extra arguments (See LaTeX Companion, p.866) # for height and inner-pos. We're ignoring inner-pos, for now, though. DefMacro('\parbox[] [] [] {Dimension}{}', -'\lx@hidden@bgroup\hsize=#4\textwidth\hsize\columnwidth\hsize\ifx.#2.\lx@parbox[#1]{#4}{#5}\else\lx@parbox[#1][#2][#3]{#4}{#5}\fi\lx@hidden@egroup'); + '\lx@hidden@bgroup\hsize=#4\textwidth\hsize\columnwidth\hsize' + . '\parindent\z@\parskip\z@skip' + . '\ifx.#2.\lx@parbox[#1]{#4}{#5}\else\lx@parbox[#1][#2][#3]{#4}{#5}\fi\lx@hidden@egroup'); DefConstructor('\lx@parbox[][Dimension] OptionalUndigested {Dimension} VBoxContents', sub { my ($document, $attachment, $b, $c, $width, $body, %props) = @_; diff --git a/lib/LaTeXML/resources/CSS/LaTeXML.css b/lib/LaTeXML/resources/CSS/LaTeXML.css index 758ff3d0c..aeafa34fd 100644 --- a/lib/LaTeXML/resources/CSS/LaTeXML.css +++ b/lib/LaTeXML/resources/CSS/LaTeXML.css @@ -336,7 +336,7 @@ dl.ltx_description dl.ltx_description dd { margin-left:3em; } max-width:0em; text-align:right; } */ .ltx_parbox { - text-indent:0em; + text-indent:0em !important; display: inline-block; } /* NOTE that it is CRITICAL to put position:relative outside & absolute inside!! From 43d048e7571169c2809e317f27fcfed57d2ba4dd Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 28 May 2026 17:54:51 -0400 Subject: [PATCH 29/31] Improve itemization sizing --- .../Engine/latex_constructs.pool.ltxml | 59 +++++++++---------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml index 5df0b57ad..3fef2ca39 100644 --- a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml +++ b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml @@ -1031,22 +1031,22 @@ DefMacro('\@dbltoplist', ''); DefMacro('\@dbldeferlist', ''); DefMacro('\@startcolumn', ''); # Style parameters from Fig. C.3, p.182 -DefRegister('\paperheight' => Dimension('11in')); -DefRegister('\paperwidth' => Dimension('8.5in')); -DefRegister('\textheight' => Dimension('550pt')); -DefRegister('\textwidth' => Dimension('345pt')); -DefRegister('\topmargin' => Dimension(0)); -DefRegister('\headheight' => Dimension(0)); -DefRegister('\headsep' => Dimension(0)); -DefRegister('\footskip' => Dimension(0)); -DefRegister('\footheight' => Dimension(0)); -DefRegister('\evensidemargin' => Dimension(0)); -DefRegister('\oddsidemargin' => Dimension(0)); -DefRegister('\marginparwidth' => Dimension(0)); -DefRegister('\marginparsep' => Dimension(0)); -DefRegister('\columnwidth' => Dimension('6in')); -DefRegister('\linewidth' => Dimension('6in')); -DefRegister('\baselinestretch' => Dimension(0)); +DefRegister('\paperheight' => Dimension('11in')); +DefRegister('\paperwidth' => Dimension('8.5in')); +DefRegister('\textheight' => Dimension('550pt')); +DefRegister('\textwidth' => Dimension('345pt')); +DefRegister('\topmargin' => Dimension(0)); +DefRegister('\headheight' => Dimension(0)); +DefRegister('\headsep' => Dimension(0)); +DefRegister('\footskip' => Dimension(0)); +DefRegister('\footheight' => Dimension(0)); +DefRegister('\evensidemargin' => Dimension(0)); +DefRegister('\oddsidemargin' => Dimension(0)); +DefRegister('\marginparwidth' => Dimension(0)); +DefRegister('\marginparsep' => Dimension(0)); +DefRegister('\columnwidth' => Dimension('6in')); +DefRegister('\linewidth' => Dimension('6in')); +DefMacro('\baselinestretch' => '1'); #====================================================================== # C.5.4 The Title Page and Abstract @@ -1352,7 +1352,8 @@ sub beginItemize { ResetCounter($usecounter); } ## return RefStepCounter('@itemize' . $listpostfix); } return (RefStepCounter('@itemize' . $listpostfix), - counter => $usecounter, series => $series); } # So end can save counter value + counter => $usecounter, series => $series, # So end can save counter value + padtop => LookupDimension('\topsep')); } # These counters are ONLY used for id's of ALL the various itemize, enumerate, etc elements # Only create the 1st level (so that binding style can start numbering 'within' appropriately) @@ -1367,12 +1368,9 @@ sub RefStepItemCounter { AssignValue(itemization_items => $n + 1); my %attr = (); my $sep = LookupDimension('\itemsep'); - if (($n > 0) && $sep && ($sep->valueOf != LookupDimension('\lx@default@itemsep')->valueOf)) { - $attr{itemsep} = $sep; - # this SHOULD pad the item, but \item is ONLY the marker, not the item content! -## $attr{padtop} = $sep; -## $attr{height} = Dimension('1em'); - } + if (($n > 0) && $sep) { + if ($sep->valueOf != LookupDimension('\lx@default@itemsep')->valueOf) { + $attr{itemsep} = $sep; } } if (defined $tag) { my @props = RefStepID($counter); if (IsEmpty($tag)) { # empty tag? @@ -1449,12 +1447,11 @@ sub setEnumerationStyle { return; } # End paragraphs, like \par, but only if within an item's text -DefConstructorI('\preitem@par', undef, sub { - my ($document, %props) = @_; - if (!$props{inPreamble} && !$document->getNodeQName($document->getElement(), 'ltx:itemize')) { - $document->maybeCloseElement('ltx:p'); - $document->maybeCloseElement('ltx:para'); } }, - alias => '\par'); +# Also, add a vskip, between for sizing +DefMacro('\preitem@par', sub { + return ((LookupValue('itemization_items') || 0) > 0 + ? (T_CS('\par'), Invocation(T_CS('\vskip'), T_CS('\itemsep'))) + : ()); }); # id, but NO refnum (et.al) attributes on itemize \item ... # unless the optional tag argument was given! @@ -2976,7 +2973,7 @@ sub defineNewTheorem { T_CS('\let'), T_CS('\the' . $counter), T_CS('\@empty'), Invocation(T_CS('\lx@make@tags'), T_OTHER($thmset)), T_END); - } } + } } else { %ctr = RefStepCounter($thmset); } } my $title = DigestText(Tokens(T_BEGIN, @@ -3324,7 +3321,7 @@ sub arrange_panels_and_breaks { my $block = $document->wrapNodes('ltx:block', $prev_node, $child); push(@row_contents, [$block, 'ltx:block', $prev_width + $child_width]); pop(@all_panels); push(@all_panels, $block); - } } + } } else { # otherwise keep the last panel as-is, and append a sibling push(@row_contents, $prev_panel); From 988c737069cfd7060465eee8e48f1b4e2bcf2e6f Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 28 May 2026 17:57:59 -0400 Subject: [PATCH 30/31] Use dumped vspace macros, when possible; implement more of setspace.sty --- lib/LaTeXML/Engine/latex_base.pool.ltxml | 68 +++++++++---------- .../Engine/latex_constructs.pool.ltxml | 3 - lib/LaTeXML/Package/setspace.sty.ltxml | 9 +-- 3 files changed, 38 insertions(+), 42 deletions(-) diff --git a/lib/LaTeXML/Engine/latex_base.pool.ltxml b/lib/LaTeXML/Engine/latex_base.pool.ltxml index 8a1ebce94..834ea5bfa 100644 --- a/lib/LaTeXML/Engine/latex_base.pool.ltxml +++ b/lib/LaTeXML/Engine/latex_base.pool.ltxml @@ -32,8 +32,8 @@ use List::Util qw(min max); # C.0 Prelminaries & Shorthands #====================================================================== -Let('\@pushfilename','\lx@pushfilename'); -Let('\@popfilename', '\lx@popfilename'); +Let('\@pushfilename', '\lx@pushfilename'); +Let('\@popfilename', '\lx@popfilename'); DefMacroI('\@ehc', undef, "I can't help"); @@ -139,18 +139,18 @@ DefMacro('\@xviipt', '17.28'); DefMacro('\@xxpt', '20.74'); DefMacro('\@xxvpt', '24.88'); # LaTeX 209 stuff -DefMacro('\vpt', '\edef\f@size{\@vpt}\rm'); -DefMacro('\vipt', '\edef\f@size{\@vipt}\rm'); -DefMacro('\viipt', '\edef\f@size{\@viipt}\rm'); -DefMacro('\viiipt', '\edef\f@size{\@viiipt}\rm'); -DefMacro('\ixpt', '\edef\f@size{\@ixpt}\rm'); -DefMacro('\xpt', '\edef\f@size{\@xpt}\rm'); -DefMacro('\xipt', '\edef\f@size{\@xipt}\rm'); -DefMacro('\xiipt', '\edef\f@size{\@xiipt}\rm'); -DefMacro('\xivpt', '\edef\f@size{\@xivpt}\rm'); -DefMacro('\xviipt', '\edef\f@size{\@xviipt}\rm'); -DefMacro('\xxpt', '\edef\f@size{\@xxpt}\rm'); -DefMacro('\xxvpt', '\edef\f@size{\@xxvpt}\rm'); +DefMacro('\vpt', '\edef\f@size{\@vpt}\rm'); +DefMacro('\vipt', '\edef\f@size{\@vipt}\rm'); +DefMacro('\viipt', '\edef\f@size{\@viipt}\rm'); +DefMacro('\viiipt', '\edef\f@size{\@viiipt}\rm'); +DefMacro('\ixpt', '\edef\f@size{\@ixpt}\rm'); +DefMacro('\xpt', '\edef\f@size{\@xpt}\rm'); +DefMacro('\xipt', '\edef\f@size{\@xipt}\rm'); +DefMacro('\xiipt', '\edef\f@size{\@xiipt}\rm'); +DefMacro('\xivpt', '\edef\f@size{\@xivpt}\rm'); +DefMacro('\xviipt', '\edef\f@size{\@xviipt}\rm'); +DefMacro('\xxpt', '\edef\f@size{\@xxpt}\rm'); +DefMacro('\xxvpt', '\edef\f@size{\@xxvpt}\rm'); #********************************************************************** # Basic \documentclass & \documentstyle @@ -268,8 +268,8 @@ DefMacro('\@restorepar', '\def\par{\@par}'); NewCounter('footnote'); DefMacroI('\thefootnote', undef, '\arabic{footnote}'); NewCounter('mpfootnote'); -DefMacroI('\thempfn', undef, '\thefootnote'); -DefMacroI('\thempfootnote', undef, '\arabic{mpfootnote}'); +DefMacroI('\thempfn', undef, '\thefootnote'); +DefMacroI('\thempfootnote', undef, '\arabic{mpfootnote}'); DefRegister('\footnotesep' => Dimension(0)); #====================================================================== # C.3.4 Accents and Special Symbols @@ -291,9 +291,9 @@ DefMacroI('\appendixesname', undef, 'Appendixes'); # C.4.3 Table of Contents #====================================================================== # Insert stubs that will be filled in during post processing. -DefMacroI('\contentsname', undef, 'Contents'); +DefMacroI('\contentsname', undef, 'Contents'); DefMacroI('\listfigurename', undef, 'List of Figures'); -DefMacroI('\listtablename', undef, 'List of Tables'); +DefMacroI('\listtablename', undef, 'List of Tables'); #====================================================================== # C.4.4 Style registers #====================================================================== @@ -357,11 +357,10 @@ DefMacro('\subparagraphmark{}', Tokens()); DefMacro('\@tabacckludge {}', '\csname\string#1\endcsname'); DefPrimitive('\DeclareTextAccent DefToken {}{}', sub { - ignoredDefinition('DeclareTextAccent', $_[1]); }); + ignoredDefinition('DeclareTextAccent', $_[1]); }); DefPrimitive('\DeclareTextAccentDefault{}{}', sub { ignoredDefinition('DeclareTextAccentDefault', $_[1]); }); - DefPrimitive('\DeclareTextComposite{}{}{}{}', sub { ignoredDefinition('DeclareTextComposite', $_[1]); }); DefPrimitive('\DeclareTextCompositeCommand{}{}{}{}', @@ -401,17 +400,17 @@ DefMacroI('\floatpagefraction', undef, "0.25"); NewCounter('dbltopnumber'); DefMacroI('\dbltopfraction', undef, "0.7"); DefMacroI('\dblfloatpagefraction', undef, "0.25"); -DefRegister('\floatsep' => Glue('12.0pt plus 2.0pt minus 2.0pt')); -DefRegister('\textfloatsep' => Glue('20.0pt plus 2.0pt minus 4.0pt')); -DefRegister('\intextsep' => Glue('12.0pt plus 2.0pt minus 2.0pt')); -DefRegister('\dblfloatsep' => Glue('12.0pt plus 2.0pt minus 2.0pt')); -DefRegister('\dbltextfloatsep' => Glue('20.0pt plus 2.0pt minus 4.0pt')); -DefRegister('\@fptop' => Glue(0)); -DefRegister('\@fpsep' => Glue(0)); -DefRegister('\@fpbot' => Glue(0)); -DefRegister('\@dblfptop' => Glue(0)); -DefRegister('\@dblfpsep' => Glue(0)); -DefRegister('\@dblfpbot' => Glue(0)); +DefRegister('\floatsep' => Glue('12.0pt plus 2.0pt minus 2.0pt')); +DefRegister('\textfloatsep' => Glue('20.0pt plus 2.0pt minus 4.0pt')); +DefRegister('\intextsep' => Glue('12.0pt plus 2.0pt minus 2.0pt')); +DefRegister('\dblfloatsep' => Glue('12.0pt plus 2.0pt minus 2.0pt')); +DefRegister('\dbltextfloatsep' => Glue('20.0pt plus 2.0pt minus 4.0pt')); +DefRegister('\@fptop' => Glue(0)); +DefRegister('\@fpsep' => Glue(0)); +DefRegister('\@fpbot' => Glue(0)); +DefRegister('\@dblfptop' => Glue(0)); +DefRegister('\@dblfpsep' => Glue(0)); +DefRegister('\@dblfpbot' => Glue(0)); Let('\topfigrule', '\relax'); Let('\botfigrule', '\relax'); Let('\dblfigrule', '\relax'); @@ -485,7 +484,6 @@ RawTeX(<<'EOL'); \DeclareRobustCommand\usebox[1]{\leavevmode\copy #1\relax} EOL - #********************************************************************** # C.14 Pictures and Color #********************************************************************** @@ -597,7 +595,6 @@ EoTeX #====================================================================== - RawTeX(<<'EOTeX'); \chardef\@xxxii=32 \mathchardef\@Mi=10001 @@ -802,8 +799,9 @@ RawTeX(<<'EOTeX'); \def\glb@settings{}% EOTeX - - +DefPrimitive('\addvspace {}', undef); +DefPrimitive('\addpenalty {}', undef); +DefPrimitiveI('\@endparenv'); #====================================================================== DefMacroI('\loggingall', undef, Tokens()); diff --git a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml index 3fef2ca39..0c2e2cb2b 100644 --- a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml +++ b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml @@ -4647,9 +4647,6 @@ DefPrimitive('\hspace OptionalMatch:* {Dimension}', sub { width => $length, isSpace => 1); }); DefMacro('\vspace OptionalMatch:* {}', '\vskip #2\relax'); -DefPrimitive('\addvspace {}', undef); -DefPrimitive('\addpenalty {}', undef); -DefPrimitiveI('\@endparenv'); # \hfill, \vfill diff --git a/lib/LaTeXML/Package/setspace.sty.ltxml b/lib/LaTeXML/Package/setspace.sty.ltxml index 54c5a61f5..bcf208c12 100644 --- a/lib/LaTeXML/Package/setspace.sty.ltxml +++ b/lib/LaTeXML/Package/setspace.sty.ltxml @@ -26,10 +26,11 @@ DefEnvironment("{onehalfspace}", "#body"); DefEnvironment("{doublespace}", "#body"); DefEnvironment("{spacing}{}", "#body"); -DefMacroI('\setstretch', '{}', ''); -DefMacroI('\SetSinglespace', '{}', ''); -DefMacroI('\setdisplayskipstretch', '{}', ''); -DefMacroI('\restore@spacing', undef, ''); +DefMacro('\setspace@singlespace', '1'); +DefMacro('\setstretch{}', '\def\baselinestretch{#1}'); +DefMacro('\SetSinglespace{}', '\def\setspace@singlespace{#1}'); +DefMacro('\setdisplayskipstretch{}', ''); +DefMacro('\restore@spacing', ''); #********************************************************************** 1; From 2d3c6db31314277a34e6c0776b7a63453331751e Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 28 May 2026 18:12:44 -0400 Subject: [PATCH 31/31] Set svg size in em units (viewbox will scale to fit that size), then set foreignObject fontsize which WILL get rescaled by svg to match browser's current fontsize (surprisingly); update xy test case accordingly --- lib/LaTeXML/Engine/TeX_Box.pool.ltxml | 5 +-- lib/LaTeXML/Package/pgfsys-latexml.def.ltxml | 7 +++- lib/LaTeXML/Package/xy.tex.ltxml | 42 +++++++++---------- t/graphics/xytest.xml | 44 ++++++++++---------- 4 files changed, 50 insertions(+), 48 deletions(-) diff --git a/lib/LaTeXML/Engine/TeX_Box.pool.ltxml b/lib/LaTeXML/Engine/TeX_Box.pool.ltxml index 11fa3ab1d..e7eaaea47 100644 --- a/lib/LaTeXML/Engine/TeX_Box.pool.ltxml +++ b/lib/LaTeXML/Engine/TeX_Box.pool.ltxml @@ -385,7 +385,8 @@ Tag('svg:foreignObject', autoOpen => 1, autoClose => 1, $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;'); + . '--ltx-fo-depth:' . $d->emValue(undef, $f) . 'em;' + . 'font-size:' . $f->getSize . 'pt;'); } }); sub isVAttached { @@ -418,8 +419,6 @@ 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}) { - $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); $document->absorb($contents); diff --git a/lib/LaTeXML/Package/pgfsys-latexml.def.ltxml b/lib/LaTeXML/Package/pgfsys-latexml.def.ltxml index 6d44b4072..8ab9a5db6 100644 --- a/lib/LaTeXML/Package/pgfsys-latexml.def.ltxml +++ b/lib/LaTeXML/Package/pgfsys-latexml.def.ltxml @@ -84,10 +84,13 @@ DefConstructor('\lxSVG@insertpicture{}', sub { $document->absorb($content); $document->closeElement('svg:g'); } else { + my $font = $props{_font}; $document->openElement('ltx:picture'); $document->openElement('svg:svg', version => "1.1", - width => $props{pxwidth}, height => $props{pxheight}, style => $props{style}, + width => $props{width}->emValue(2, $font) . 'em', + height => $props{height}->emValue(2, $font) . 'em', + style => $props{style}, viewBox => "$props{minx} $props{miny} $props{pxwidth} $props{pxheight}", overflow => "visible"); my $x0 = -(0 + $props{minx}); @@ -134,6 +137,8 @@ DefConstructor('\lxSVG@insertpicture{}', sub { $whatsit->setProperty(pxwidth => $w); $whatsit->setProperty(pxheight => $h); $whatsit->setProperty(style => "vertical-align:" . $base . "px") if $base; + $whatsit->setProperty(_font => $whatsit->getFont); + # or tikz macro (see corescopes) return; }, # \pgfpicture seems to make a 0 sized box, which throws off our postprocessor diff --git a/lib/LaTeXML/Package/xy.tex.ltxml b/lib/LaTeXML/Package/xy.tex.ltxml index cb1105ec2..410c44ff2 100644 --- a/lib/LaTeXML/Package/xy.tex.ltxml +++ b/lib/LaTeXML/Package/xy.tex.ltxml @@ -93,20 +93,18 @@ DefConstructor('\lx@xy@svgnested Digested', return (width => $w, height => $y1, depth => $y0->negate, transform => $transform); }); DefConstructor('\lx@xy@svg Digested', sub { -# "" -# . "" -# . "#1" -# . "", - my ($document,$content,%props) = @_; + my ($document, $content, %props) = @_; + my $font = $props{_font}; $document->openElement('ltx:picture'); $document->openElement('svg:svg', - version => "1.1", overflow => "visible", - width => $props{pxwidth}, height => $props{pxheight}, style => $props{style}, - viewBox => "$props{minx} $props{miny} $props{pxwidth} $props{pxheight}"); - $document->openElement('svg:g',transform => $props{transform}, _scopebegin => 1); + version => "1.1", overflow => "visible", + width => $props{width}->emValue(2, $font) . 'em', + height => $props{height}->emValue(2, $font) . 'em', + style => $props{style}, + viewBox => "$props{minx} $props{miny} $props{pxwidth} $props{pxheight}"); + $document->openElement('svg:g', transform => $props{transform}, _scopebegin => 1); addSVGDebuggingBox($document, - $props{x},$props{y},$props{width},$props{height},'#FF00FF') + $props{x}, $props{y}, $props{width}, $props{height}, '#FF00FF') if $LaTeXML::DEBUG{svg}; $document->absorb($content); $document->closeElement('svg:g'); @@ -118,26 +116,26 @@ DefConstructor('\lx@xy@svg Digested', sub { || [Dimension(0), Dimension(0), Dimension(0), Dimension(0)] }; Debug("XY: " . ToString($x0) . ' ' . ToString($y0) . ' ' . ToString($x1) . ' ' . ToString($y1)) if $LaTeXML::DEBUG{svg_verbose}; - my $w = $x1->subtract($x0); - my $h = $y1->subtract($y0); - if($w->valueOf < 0){ # Rarely, the range hasn't actually been set?!? - $x0 = $x1 = Dimension(0); } - if($h->valueOf < 0){ - $y0 = $y1 = Dimension(0); } + my $w = $x1->subtract($x0); + my $h = $y1->subtract($y0); + if ($w->valueOf < 0) { # Rarely, the range hasn't actually been set?!? + $x0 = $x1 = Dimension(0); } + if ($h->valueOf < 0) { + $y0 = $y1 = Dimension(0); } my $x = $x0->negate; my $y = $y1->subtract($y0); my $minx = $x->pxValue; my $miny = $y0->negate->pxValue; my $transform = "matrix(1 0 0 -1 " . $x->pxValue . ' ' . $y->pxValue . ')'; - my $style = ($miny ? "vertical-align:".(-$miny)."px" : undef); - my $pxwidth = max($w->pxValue,1); - my $pxheight = max($h->pxValue,1); + my $style = ($miny ? "vertical-align:" . (-$miny) . "px" : undef); + my $pxwidth = max($w->pxValue, 1); + my $pxheight = max($h->pxValue, 1); Debug("XY size: " . ToString($w) . ' x ' . ToString($h) . ' + ' . 0 . ' @ ' . ToString($x) . ' x ' . ToString($y)) if $LaTeXML::DEBUG{svg_verbose}; return (x => $x0, y => $y0, width => $w, height => $h, - pxwidth => $pxwidth, pxheight => $pxheight, - minx => $minx, miny => $miny, style => $style, + pxwidth => $pxwidth, pxheight => $pxheight, + minx => $minx, miny => $miny, style => $style, transform => $transform); }); DefPrimitive('\lx@xy@capturerange', sub { diff --git a/t/graphics/xytest.xml b/t/graphics/xytest.xml index b7cf6c016..05c3a0d63 100644 --- a/t/graphics/xytest.xml +++ b/t/graphics/xytest.xml @@ -8,17 +8,17 @@ - + - + A - + B @@ -34,17 +34,17 @@ - + - + U - + y @@ -63,7 +63,7 @@ - + x @@ -75,7 +75,7 @@ - + @@ -91,7 +91,7 @@ - + q @@ -105,7 +105,7 @@ - + p @@ -118,7 +118,7 @@ - + X @@ -126,7 +126,7 @@ - + f @@ -139,7 +139,7 @@ - + Y @@ -147,7 +147,7 @@ - + g @@ -160,7 +160,7 @@ - + Z @@ -179,10 +179,10 @@ = x - + - + A @@ -210,21 +210,21 @@ - + B - + C - D + D @@ -247,13 +247,13 @@ [ - + - +
The Table
or similar. +# These (should) appear in an internal_vertical context, +# BUT the arg should be processed to a horizontal (paragraph) List! DefConstructor('\@@caption{}', "^^#1", - sizer => 0, mode => 'restricted_horizontal', + sizer => '#1', + beforeDigest => sub { + $_[0]->beginMode('internal_vertical'); + $_[0]->enterHorizontal; }, + afterDigest => sub { + $_[0]->leaveHorizontal_internal; + $_[0]->endMode('internal_vertical') }, ); DefConstructor('\@@toccaption{}', "^^#1", - sizer => 0, mode => 'restricted_horizontal', + sizer => 0, + beforeDigest => sub { + $_[0]->beginMode('internal_vertical'); + $_[0]->enterHorizontal; }, + afterDigest => sub { + $_[0]->leaveHorizontal_internal; + $_[0]->endMode('internal_vertical') }, ); sub beforeFloat { From 1abcf815805470bf964fb87a9d9a03757183123e Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 30 Apr 2026 18:29:58 -0400 Subject: [PATCH 12/31] Update tests for caption change --- t/graphics/graphrot.xml | 2 +- t/structure/figures.xml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/t/graphics/graphrot.xml b/t/graphics/graphrot.xml index b4df83285..9cb6f16ea 100644 --- a/t/graphics/graphrot.xml +++ b/t/graphics/graphrot.xml @@ -395,7 +395,7 @@ the whales Save the whale Save the + Table 5 Table 5 diff --git a/t/structure/figures.xml b/t/structure/figures.xml index ad3567cbe..c4a990cce 100644 --- a/t/structure/figures.xml +++ b/t/structure/figures.xml @@ -52,6 +52,7 @@ (a)This is a figure (a)This is a figure + (b) @@ -105,6 +106,7 @@ and collectively as . (a)This is a figure (a)This is a figure + (b) @@ -133,6 +135,7 @@ and collectively as . (a)This is a table [Tabular One] + (b) @@ -188,6 +191,7 @@ and collectively as . (a)This is a table [Tabular One] + (b) From 53489cbb12804364d0ca93f81f5969c66817941c Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 30 Apr 2026 18:50:30 -0400 Subject: [PATCH 13/31] Extract internals of stomach->digestNextBody to stomach->digestUntil which does NOT rebind LaTeXML::LIST, which is needed for digesting T_BEGIN --- lib/LaTeXML/Core/Stomach.pm | 15 ++++++++++++--- lib/LaTeXML/Engine/TeX_Box.pool.ltxml | 17 +++++++++-------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/lib/LaTeXML/Core/Stomach.pm b/lib/LaTeXML/Core/Stomach.pm index dfed00105..a5b5be499 100644 --- a/lib/LaTeXML/Core/Stomach.pm +++ b/lib/LaTeXML/Core/Stomach.pm @@ -90,12 +90,21 @@ sub getScriptLevel { # It puts a lot of cruft in Gullet; Should we just create a new Gullet? sub digestNextBody { + my ($self, $terminal) = @_; + no warnings 'recursion'; + local @LaTeXML::LIST = (); + digestUntil($self, $terminal); + return @LaTeXML::LIST; } + +# This method digests content until $terminal or closing initial mode, +# pushing onto @LaTeXML::LIST. +# But, unlike digestNextBody, it does NOT bind @LaTeXML::LIST, nor return anything. +sub digestUntil { my ($self, $terminal) = @_; no warnings 'recursion'; my $startloc = getLocator($self); my $initdepth = scalar(@{ $$self{boxing} }); my $token; - local @LaTeXML::LIST = (); my $alignment = $STATE->lookupValue('Alignment'); my @aug = (); @@ -108,7 +117,7 @@ sub digestNextBody { # at least \over calls in here without the intent to passing through the alignment. # So if we already have some digested boxes available, return them here. $$self{gullet}->unread($token); - return @LaTeXML::LIST; } + return; } my @r = invokeToken($self, $token); push(@LaTeXML::LIST, @r); push(@aug, $token, @r); @@ -119,7 +128,7 @@ sub digestNextBody { "Got " . join("\n -- ", map { Stringify($_) } @aug)) if $terminal && !Equals($token, $terminal); push(@LaTeXML::LIST, Box()) unless $token; # Dummy `trailer' if none explicit. - return @LaTeXML::LIST; } + return; } # Digest a list of tokens independent from any current Gullet. # Typically used to digest arguments to primitives or constructors. diff --git a/lib/LaTeXML/Engine/TeX_Box.pool.ltxml b/lib/LaTeXML/Engine/TeX_Box.pool.ltxml index 496dfb06b..11fa3ab1d 100644 --- a/lib/LaTeXML/Engine/TeX_Box.pool.ltxml +++ b/lib/LaTeXML/Engine/TeX_Box.pool.ltxml @@ -27,18 +27,19 @@ $LaTeXML::DEBUG{svg} = 1 if $$LaTeXML::DEBUG{svg_verbose}; # These are actually TeX primitives, but we treat them as a Whatsit so they # remain in the constructed tree. -#DefConstructor('{','#body', beforeDigest=>sub{$_[0]->bgroup;}, captureBody=>1); -######DefPrimitive('{', sub { DefPrimitive(T_BEGIN, sub { my ($stomach) = @_; $stomach->bgroup; - my $open = Box(undef, undef, undef, T_BEGIN, isEmpty => 1, alignmentSkippable => 1); - my $ismath = $STATE->lookupValue('IN_MATH'); - my $mode = $STATE->lookupValue('MODE'); - my @body = $stomach->digestNextBody(); - return ($ismath ? List($open, @body, mode => $mode) : ($open, @body)); }); + my $open = Box(undef, undef, undef, T_BEGIN, isEmpty => 1, alignmentSkippable => 1); + my $mode = $STATE->lookupValue('MODE'); + if ($STATE->lookupValue('IN_MATH')) { # In math, we return a Math List + my @body = $stomach->digestNextBody(); + return List($open, @body, mode => $mode); } + else { # Else just digest directly to @LaTeXML::LIST + push(@LaTeXML::LIST, $open); + $stomach->digestUntil(); + return; } }); -#######DefPrimitive('}', sub { DefPrimitive(T_END, sub { my $f = LookupValue('font'); $_[0]->egroup; From 3cc1eb9fbaa95d77e5f34863ac85f7b2911412ec Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 7 May 2026 16:23:15 -0400 Subject: [PATCH 14/31] minipage defaults to justify alignment --- lib/LaTeXML/resources/CSS/LaTeXML.css | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/LaTeXML/resources/CSS/LaTeXML.css b/lib/LaTeXML/resources/CSS/LaTeXML.css index e371a1737..4f4578337 100644 --- a/lib/LaTeXML/resources/CSS/LaTeXML.css +++ b/lib/LaTeXML/resources/CSS/LaTeXML.css @@ -562,6 +562,7 @@ cite { font-style: normal; } .ltx_minipage { align-self: normal; display: inline-block; + text-align: justify; } .ltx_minipage > .ltx_graphics { max-width:100%; From 8835f238360041ab13476c2e87268466a2b6f0cb Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 7 May 2026 16:24:08 -0400 Subject: [PATCH 15/31] Pass context as block for svg:foreignObject to avoid bad span soup --- lib/LaTeXML/resources/XSLT/LaTeXML-picture-xhtml.xsl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/LaTeXML/resources/XSLT/LaTeXML-picture-xhtml.xsl b/lib/LaTeXML/resources/XSLT/LaTeXML-picture-xhtml.xsl index 1c5400006..45dfda7a0 100644 --- a/lib/LaTeXML/resources/XSLT/LaTeXML-picture-xhtml.xsl +++ b/lib/LaTeXML/resources/XSLT/LaTeXML-picture-xhtml.xsl @@ -169,8 +169,9 @@ ltx_foreignobject_container ltx_foreignobject_content - - + + + From 7ef75c214f5ba5d274628efd4628747c497ece80 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 7 May 2026 16:26:01 -0400 Subject: [PATCH 16/31] Define methods allowing \multicolumn to REPLACE the column spec, so it gets all properties --- lib/LaTeXML/Core/Alignment.pm | 12 ++++++++++-- lib/LaTeXML/Core/Alignment/Template.pm | 9 +++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/LaTeXML/Core/Alignment.pm b/lib/LaTeXML/Core/Alignment.pm index aeccb97d6..830200e51 100644 --- a/lib/LaTeXML/Core/Alignment.pm +++ b/lib/LaTeXML/Core/Alignment.pm @@ -21,8 +21,8 @@ use LaTeXML::Common::XML; use LaTeXML::Common::Dimension; use LaTeXML::Core::Alignment::Template; use List::Util qw(max sum); -use base qw(LaTeXML::Core::Whatsit); -use base qw(Exporter); +use base qw(LaTeXML::Core::Whatsit); +use base qw(Exporter); our @EXPORT = (qw( &ReadAlignmentTemplate &MatrixTemplate)); @@ -165,6 +165,14 @@ sub currentColumn { my ($self) = @_; return $$self{current_row} && $$self{current_row}->column($$self{current_column}); } +# Used by \multicolumn to replace the current column spec with a new one +# (the row has been cloned from the overall spec) +sub replaceColumn { + my ($self, $colspec) = @_; + $$self{current_row}->replaceColumn($$self{current_column}, $colspec) + if $$self{current_row}; + return; } + sub getColumn { my ($self, $n) = @_; return $$self{current_row} && $$self{current_row}->column($n); } diff --git a/lib/LaTeXML/Core/Alignment/Template.pm b/lib/LaTeXML/Core/Alignment/Template.pm index 16cd7d889..34caa900f 100644 --- a/lib/LaTeXML/Core/Alignment/Template.pm +++ b/lib/LaTeXML/Core/Alignment/Template.pm @@ -150,6 +150,15 @@ sub column { push(@{ $$self{columns} }, {%dup}); } } } return ($n > 0 ? $$self{columns}->[$n - 1] : undef); } +# Used by \multicolumn to replace column $n spec with a new one +# (the row has been cloned from the overall spec) +sub replaceColumn { + my ($self, $n, $colspec) = @_; + my $N = scalar(@{ $$self{columns} }); + if ($n <= $N) { + $$self{columns}->[$n] = $colspec; } + return; } + sub columns { my ($self) = @_; return @{ $$self{columns} }; } From 8fb9c8b07e0fe81d847844952e347efd4b269268 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 7 May 2026 16:34:13 -0400 Subject: [PATCH 17/31] FIx column specifiers p,m,b to use \vbox so size computed correctly; Make \multicolumn copy the new spec into the alignment, so properties preserved; when storing column data get data from correct column when it was spanned --- lib/LaTeXML/Engine/TeX_Tables.pool.ltxml | 59 +++++++++++++++--------- lib/LaTeXML/Package/array.sty.ltxml | 22 +++++---- 2 files changed, 51 insertions(+), 30 deletions(-) diff --git a/lib/LaTeXML/Engine/TeX_Tables.pool.ltxml b/lib/LaTeXML/Engine/TeX_Tables.pool.ltxml index f97fa9402..9197571f1 100644 --- a/lib/LaTeXML/Engine/TeX_Tables.pool.ltxml +++ b/lib/LaTeXML/Engine/TeX_Tables.pool.ltxml @@ -66,12 +66,11 @@ DefColumnType('r', sub { DefColumnType('p{Dimension}', sub { $LaTeXML::BUILD_TEMPLATE->addColumn( - before => Tokens(T_CS('\vtop'), T_BEGIN, T_CS('\hbox'), - T_LETTER('t'), T_LETTER('o'), $_[1]->revert, T_CS('\relax'), - T_BEGIN), + before => Tokens(T_CS('\vtop'), T_BEGIN, T_BEGIN, + T_CS('\hsize'), $_[1]->revert, T_CS('\relax')), after => Tokens(T_END, T_END), vattach => 'top', - align => 'justify', + align => 'justify', width => $_[1], ); return; }); DefColumnType('*{Number}{}', sub { @@ -170,7 +169,7 @@ DefConstructor('\halign BoxSpecification', beforeConstruct => sub { my ($document) = @_; $document->maybeCloseElement('ltx:p'); }, - afterDigest => sub { # After spec, but before body! + afterDigest => sub { # After spec, but before body! my ($stomach, $whatsit) = @_; $whatsit->setProperty(mode => 'internal_vertical'); $stomach->beginMode('restricted_horizontal'); @@ -360,7 +359,7 @@ sub digestAlignmentColumn { my ($stomach, $alignment, $lastwascr) = @_; my $gullet = $stomach->getGullet; my $ismath = $STATE->lookupValue('IN_MATH'); - my $mode = $STATE->lookupValue('MODE'); + my $mode = $STATE->lookupValue('MODE'); local @LaTeXML::LIST = (); # Scan for leading \omit, skipping over (& saving) \hline. Debug("Halign $alignment: COLUMN starting scan ($mode)") if $LaTeXML::DEBUG{halign}; @@ -456,16 +455,19 @@ sub trimColumnTemplate { # Given the boxes for an alignment cell, # extract & remove the various fills and rules from the ends to annotate the cell structure +# Note: With spanned columns (\multicolumn) \omit has moved to the LAST column of span, +# but we want to store data in the FIRST column, to be consistent with rowspan. sub extractAlignmentColumn { my ($alignment, $boxes) = @_; return () unless $alignment; # ?? # Note: $n0,$n1 is a VERY round-about way of tracking the column spanning! - my $ismath = $STATE->lookupValue('IN_MATH'); - my $n0 = (LookupValue('alignmentStartColumn') || 0) + 1; - my $n1 = $alignment->currentColumnNumber; - my $colspec = $alignment->getColumn($n0); - my $align = $$colspec{align} || 'left'; - my $border = ''; + my $ismath = $STATE->lookupValue('IN_MATH'); + my $n0 = (LookupValue('alignmentStartColumn') || 0) + 1; + my $n1 = $alignment->currentColumnNumber; + my $colspec0 = $alignment->getColumn($n0); # Where we'll PUT the info + my $colspec1 = $alignment->getColumn($n1); # But this is the spec USED + my $align = $$colspec1{align} || 'left'; + my $border = ''; # Peel off any boxes from both sides until we get the "meat" of the column. # from this we can establish borders, alignment and emptiness. # But we, of course, immediately put them back... @@ -473,7 +475,7 @@ sub extractAlignmentColumn { my @saveleft = (); my @saveright = (); my (@lspaces, @rspaces); - if (my $skip = $$colspec{tabskip}) { + if (my $skip = $$colspec1{tabskip}) { push(@lspaces, Digest(Tokens(T_CS('\hskip'), $skip->revert, T_CS('\relax')))); } while (@boxes) { if (ref $boxes[0] eq 'LaTeXML::Core::List') { @@ -515,24 +517,26 @@ sub extractAlignmentColumn { unshift(@saveright, pop(@boxes)); } else { last; } } - delete $$colspec{width} unless $align eq 'justify'; + delete $$colspec0{width} unless $align eq 'justify'; # Replacing boxes with the fil padding & vertical rules stripped off @boxes = (@saveleft, @boxes, @saveright); $boxes = List(@boxes, mode => $boxes->getProperty('mode')); # record relevant info in the Alignment. - $$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; + $$colspec0{align} = $align; + $$colspec0{width} = $$colspec1{width}; + $$colspec0{border} = $border = ($$colspec1{border} || '') . $border; + $$colspec0{boxes} = $boxes; + $$colspec0{lspaces} = List(@lspaces) if @lspaces; + $$colspec0{rspaces} = List(@rspaces) if @rspaces; + $$colspec0{colspan} = $n1 - $n0 + 1; if ($$alignment{in_tabular_head} || $$alignment{in_tabular_foot}) { - $$colspec{thead}{column} = 1; } + $$colspec0{thead}{column} = 1; } + for (my $i = $n0 + 1 ; $i <= $n1 ; $i++) { my $c = $alignment->getColumn($i); $$c{skipped} = 1 if $c; } - Debug("Halign $alignment: INSTALL column " . join(',', map { $_ . "=" . ToString($$colspec{$_}); } sort keys %$colspec)) if $LaTeXML::DEBUG{halign}; + Debug("Halign $alignment: INSTALL column " . join(',', map { $_ . "=" . ToString($$colspec0{$_}); } sort keys %$colspec0)) if $LaTeXML::DEBUG{halign}; return $boxes; } #====================================================================== @@ -691,6 +695,17 @@ DefMacro('\lx@alignment@multicolumn {Number} AlignmentTemplate {}', sub { $tokens->unlist, ($column ? afterCellUnlist($$column{after}) : ())); }); +DefMacro('\lx@alignment@multicolumn {Number}{}{}', sub { + my ($gullet, $span, $template, $tokens) = @_; + $span = $span->valueOf; + # First part, like \multispan + (T_CS('\omit'), (map { (T_CS('\span'), T_CS('\omit')) } 1 .. $span - 1), + T_CS('\lx@alignment@altcolumn'), T_BEGIN, $template, T_END, $tokens); }); +DefMacro('\lx@alignment@altcolumn AlignmentTemplate', sub { + my ($gullet, $template) = @_; + if (my $alignment = LookupValue('Alignment')) { + $alignment->replaceColumn($template->column(1)); } }); + DefConditionalI('\if@in@lx@alignment', undef, sub { LookupValue('Alignment'); }); DefPrimitive('\lx@alignment@bindings AlignmentTemplate []', sub { diff --git a/lib/LaTeXML/Package/array.sty.ltxml b/lib/LaTeXML/Package/array.sty.ltxml index 47dec92c1..e95d6e9f9 100644 --- a/lib/LaTeXML/Package/array.sty.ltxml +++ b/lib/LaTeXML/Package/array.sty.ltxml @@ -24,18 +24,24 @@ DefMacroI('\lasthline', undef, '\hline'); DefColumnType('>{}', sub { $LaTeXML::BUILD_TEMPLATE->addBeforeColumn($_[1]->unlist); return; }); DefColumnType('<{}', sub { $LaTeXML::BUILD_TEMPLATE->addAfterColumn($_[1]->unlist); return; }); -# This is the same as p +# This is the same as p, but using \vtop # but really needs to specify vertical alignment as centered DefColumnType('m{Dimension}', sub { - $LaTeXML::BUILD_TEMPLATE->addColumn(before => Tokens(T_CS('\vtop'), T_BEGIN), #Tokens(T_BEGIN), - after => Tokens(T_END), - align => 'justify', width => $_[1], vattach => 'middle'); return; }); -# This is also the same as p + $LaTeXML::BUILD_TEMPLATE->addColumn( + before => Tokens(T_CS('\vbox'), T_BEGIN, T_BEGIN, + T_CS('\hsize'), $_[1]->revert, T_CS('\relax')), + after => Tokens(T_END, T_END), + align => 'justify', width => $_[1], + vattach => 'middle'); return; }); +# This is also the same as p, but SHOULD use \vbot (if there was one) # but really needs to specify vertical alignment as bottom DefColumnType('b{Dimension}', sub { - $LaTeXML::BUILD_TEMPLATE->addColumn(before => Tokens(T_CS('\vbox'), T_BEGIN), #Tokens(T_BEGIN), - after => Tokens(T_END), - align => 'justify', width => $_[1], vattach => 'bottom'); return; }); + $LaTeXML::BUILD_TEMPLATE->addColumn( + before => Tokens(T_CS('\vbox'), T_BEGIN, T_BEGIN, + T_CS('\hsize'), $_[1]->revert, T_CS('\relax')), + after => Tokens(T_END, T_END), + align => 'justify', width => $_[1], + vattach => 'bottom'); return; }); # Like @{}, but should NOT suppress intercolumn space DefColumnType('!{}', sub { From 4e6ba3f8604181ddf79c55701d016f75f1ab973d Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Wed, 13 May 2026 16:50:31 -0400 Subject: [PATCH 18/31] Redo p{},m{},b{} to create ltx:inline-block, so they work alongside @{}, and also process the contents with \hsize set for proper sizing --- lib/LaTeXML/Engine/TeX_Tables.pool.ltxml | 14 ++++++++++---- lib/LaTeXML/Package/array.sty.ltxml | 23 +++++++++-------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/lib/LaTeXML/Engine/TeX_Tables.pool.ltxml b/lib/LaTeXML/Engine/TeX_Tables.pool.ltxml index 9197571f1..ab924fe4b 100644 --- a/lib/LaTeXML/Engine/TeX_Tables.pool.ltxml +++ b/lib/LaTeXML/Engine/TeX_Tables.pool.ltxml @@ -64,14 +64,20 @@ DefColumnType('c', sub { DefColumnType('r', sub { $LaTeXML::BUILD_TEMPLATE->addColumn(before => Tokens(T_CS('\hfil'))); return; }); +# Note that p typesets as a paragraph of given width, +# BUT @{} might add other contents to the cell! So, we need an inline-block DefColumnType('p{Dimension}', sub { $LaTeXML::BUILD_TEMPLATE->addColumn( - before => Tokens(T_CS('\vtop'), T_BEGIN, T_BEGIN, - T_CS('\hsize'), $_[1]->revert, T_CS('\relax')), - after => Tokens(T_END, T_END), + before => Tokens(T_CS('\lx@tabular@p'), T_LETTER('t'), T_BEGIN, $_[1]->revert, T_END, T_BEGIN), + after => Tokens(T_END), vattach => 'top', - align => 'justify', width => $_[1], ); return; }); +# Make sure the content sees the desired width as \hsize +DefMacro('\lx@tabular@p{}{}', '\hsize=#2\relax\lx@tabular@p@{#1}{#2}'); +DefConstructor('\lx@tabular@p@{}{Dimension} VBoxContents', + "?#3(#3)()", + sizer => '#3', reversion => '#3', + properties => sub { (vattach => translateAttachment($_[1])); }); DefColumnType('*{Number}{}', sub { my ($gullet, $n, $pattern) = @_; diff --git a/lib/LaTeXML/Package/array.sty.ltxml b/lib/LaTeXML/Package/array.sty.ltxml index e95d6e9f9..ec26b51be 100644 --- a/lib/LaTeXML/Package/array.sty.ltxml +++ b/lib/LaTeXML/Package/array.sty.ltxml @@ -24,24 +24,19 @@ DefMacroI('\lasthline', undef, '\hline'); DefColumnType('>{}', sub { $LaTeXML::BUILD_TEMPLATE->addBeforeColumn($_[1]->unlist); return; }); DefColumnType('<{}', sub { $LaTeXML::BUILD_TEMPLATE->addAfterColumn($_[1]->unlist); return; }); -# This is the same as p, but using \vtop -# but really needs to specify vertical alignment as centered +# m{} and b{} are like p{}, but vertical alignment is middle or bottom DefColumnType('m{Dimension}', sub { $LaTeXML::BUILD_TEMPLATE->addColumn( - before => Tokens(T_CS('\vbox'), T_BEGIN, T_BEGIN, - T_CS('\hsize'), $_[1]->revert, T_CS('\relax')), - after => Tokens(T_END, T_END), - align => 'justify', width => $_[1], - vattach => 'middle'); return; }); -# This is also the same as p, but SHOULD use \vbot (if there was one) -# but really needs to specify vertical alignment as bottom + before => Tokens(T_CS('\lx@tabular@p'), T_LETTER('m'), T_BEGIN, $_[1]->revert, T_END, T_BEGIN), + after => Tokens(T_END), + vattach => 'middle', + ); return; }); DefColumnType('b{Dimension}', sub { $LaTeXML::BUILD_TEMPLATE->addColumn( - before => Tokens(T_CS('\vbox'), T_BEGIN, T_BEGIN, - T_CS('\hsize'), $_[1]->revert, T_CS('\relax')), - after => Tokens(T_END, T_END), - align => 'justify', width => $_[1], - vattach => 'bottom'); return; }); + before => Tokens(T_CS('\lx@tabular@p'), T_LETTER('b'), T_BEGIN, $_[1]->revert, T_END, T_BEGIN), + after => Tokens(T_END), + vattach => 'bottom', + ); return; }); # Like @{}, but should NOT suppress intercolumn space DefColumnType('!{}', sub { From dda21baaf3949bb804bb9bc0aa7164e4fa253618 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Wed, 13 May 2026 16:51:16 -0400 Subject: [PATCH 19/31] Update several test cases affected by the different tabular p{} processing --- t/alignment/array.xml | 16 +++--- t/alignment/cells.xml | 24 ++++---- t/alignment/colortbls.xml | 72 ++++++++++++------------ t/alignment/halignatt.xml | 2 +- t/alignment/tabular.xml | 40 +++++++------- t/babel/numprints.xml | 10 ++-- t/graphics/graphrot.xml | 100 +++++++++++++++++++++------------- t/math/array_newline_math.xml | 2 +- 8 files changed, 146 insertions(+), 120 deletions(-) diff --git a/t/alignment/array.xml b/t/alignment/array.xml index be6d249e3..4096fbc79 100644 --- a/t/alignment/array.xml +++ b/t/alignment/array.xml @@ -19,24 +19,24 @@ - - Lorem ipsum dolor sit amet, consectetur adipisicing elit + + Lorem ipsum dolor sit amet, consectetur adipisicing elit - + Lorem ipsum dolor sit amet, consectetur adipisicing elit - + Lorem ipsum dolor sit amet, consectetur adipisicing elit - - Lorem ipsum dolor sit amet, consectetur adipisicing elit + + Lorem ipsum dolor sit amet, consectetur adipisicing elit - + Lorem ipsum dolor sit amet, consectetur adipisicing elit - + Lorem ipsum dolor sit amet, consectetur adipisicing elit diff --git a/t/alignment/cells.xml b/t/alignment/cells.xml index 54cb7453f..82b74577a 100644 --- a/t/alignment/cells.xml +++ b/t/alignment/cells.xml @@ -69,8 +69,8 @@ - - Cell long text with predefined width + + Cell long text with predefined width @@ -79,8 +79,8 @@ - - Cell long… + + Cell long… @@ -148,13 +148,13 @@ - - Second multilined + + Second multilined - - column head + + column head @@ -298,8 +298,8 @@ - - Cell … + + Cell … @@ -308,8 +308,8 @@ - - Cell … + + Cell … diff --git a/t/alignment/colortbls.xml b/t/alignment/colortbls.xml index 0ab5925dc..9a086e57b 100644 --- a/t/alignment/colortbls.xml +++ b/t/alignment/colortbls.xml @@ -95,11 +95,11 @@ p{3cm}}"?> - - P-column + + P-column - - and another one + + and another one @@ -108,8 +108,8 @@ p{3cm}}"?> - Total - (wrong) + Total + (wrong) 100⋅6 @@ -117,11 +117,11 @@ p{3cm}}"?> - - Some long text in the first column + + Some long text in the first column - - bbb + + bbb @@ -130,11 +130,11 @@ p{3cm}}"?> - - aaa + + aaa - - and some long text in the second column + + and some long text in the second column @@ -143,8 +143,8 @@ p{3cm}}"?> - Total - (wrong) + Total + (wrong) 100⋅6 @@ -152,11 +152,11 @@ p{3cm}}"?> - - aaa + + aaa - - bbb + + bbb @@ -165,12 +165,12 @@ p{3cm}}"?> - - Note that the coloured rules in all columns stretch to accomodate + + Note that the coloured rules in all columns stretch to accomodate large entries in one column. - - bbb + + bbb @@ -179,11 +179,11 @@ large entries in one column. - - aaa + + aaa - - bbb + + bbb @@ -192,11 +192,11 @@ large entries in one column. - - aaa + + aaa - - Depending on your driver you may get unsightly gaps or lines + + Depending on your driver you may get unsightly gaps or lines where the ‘screens’ used to produce different shapes interact badly. You may want to cause adjacent panels of the same colour by specifying a larger overhang @@ -209,11 +209,11 @@ or by adding some negative space (in a ”“noalign” between rows. - - aaa + + aaa - - bbb + + bbb diff --git a/t/alignment/halignatt.xml b/t/alignment/halignatt.xml index 41296325b..5e81f40cd 100644 --- a/t/alignment/halignatt.xml +++ b/t/alignment/halignatt.xml @@ -53,7 +53,7 @@ .95* - * (first quarter only) + * (first quarter only) diff --git a/t/alignment/tabular.xml b/t/alignment/tabular.xml index b7cd9df21..5bcfd6edf 100644 --- a/t/alignment/tabular.xml +++ b/t/alignment/tabular.xml @@ -13,13 +13,13 @@ Price - + Year low high - Comments + Comments @@ -27,24 +27,24 @@ 1971 97– 245 - - Bad year. + + Bad year. 72 245– 245 - - Light trading due to a heavy winter. + + Light trading due to a heavy winter. 73 245– 2001 - - No gnus was very good gnus this year. + + No gnus was very good gnus this year. @@ -77,25 +77,25 @@ - 1 - a + 1 + a 2 - - b + + b 3 - - c + + c 4 - 1 - a a a a a a a a a a + 1 + a a a a a a a a a a 2 - - b + + b 3 - - c + + c 4 diff --git a/t/babel/numprints.xml b/t/babel/numprints.xml index 2062b1fee..bb89e864a 100644 --- a/t/babel/numprints.xml +++ b/t/babel/numprints.xml @@ -811,15 +811,15 @@ vs. - without braces + without braces with braces with braces and box - with braces, exp, and box + with braces, exp, and box - error + error abc def @@ -901,7 +901,7 @@ vs. rt - mor + mor e45,1 @@ -963,7 +963,7 @@ vs. txt - notblu + notblu e45,1 diff --git a/t/graphics/graphrot.xml b/t/graphics/graphrot.xml index 9cb6f16ea..6ae8076ff 100644 --- a/t/graphics/graphrot.xml +++ b/t/graphics/graphrot.xml @@ -395,7 +395,7 @@ the whales Save the whale Save the + Table 5 Table 5 @@ -413,12 +413,12 @@ the whales Save the whale Save the Pottery Flint Animal - - Stone + + Stone Other - - C14 Dates + + C14 Dates @@ -430,15 +430,28 @@ the whales Save the whale Save the Bones - + - + - Grooved Ware - + + + + + + + + + + + + + + Grooved Ware + 784 @@ -457,14 +470,14 @@ the whales Save the whale Save the × 8 - + × 2 bone - - 2150 + + 2150 ± @@ -488,12 +501,12 @@ the whales Save the whale Save the × 21 - - Hammerstone + + Hammerstone — - - — + + — @@ -513,12 +526,12 @@ the whales Save the whale Save the × 57* - - — + + — — - - 1990 + + 1990 ± @@ -546,17 +559,30 @@ the whales Save the whale Save the × 8 - - — + + — Fired clay - - — + + — + + + + + + + + + + + + + Beaker - + 552 @@ -567,12 +593,12 @@ the whales Save the whale Save the P7–14 — — - - — + + — — - - — + + — @@ -588,12 +614,12 @@ the whales Save the whale Save the — - - Quartzite-lump + + Quartzite-lump — - - — + + — @@ -609,12 +635,12 @@ the whales Save the whale Save the — - - — + + — — - - — + + — diff --git a/t/math/array_newline_math.xml b/t/math/array_newline_math.xml index 71cc48525..6eb31fd14 100644 --- a/t/math/array_newline_math.xml +++ b/t/math/array_newline_math.xml @@ -13,7 +13,7 @@ 4 - + From 004f732cc1cd48992ecdecbfb7dc42e7aa0417f3 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Tue, 26 May 2026 12:20:41 -0400 Subject: [PATCH 20/31] Support passing more size releated properties to Font: pad(top|bottom|right|left); tweak debug feedback more informative, less noisy --- lib/LaTeXML/Core/Box.pm | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/LaTeXML/Core/Box.pm b/lib/LaTeXML/Core/Box.pm index 07216bfe6..87460186e 100644 --- a/lib/LaTeXML/Core/Box.pm +++ b/lib/LaTeXML/Core/Box.pm @@ -38,7 +38,7 @@ sub Box { $properties{height} = Dimension(0) unless defined $properties{height}; $properties{depth} = Dimension(0) unless defined $properties{depth}; } my $state = $STATE; - my $mode = $properties{mode} || $state->lookupValue('MODE') || 'restricted_horizontal'; + my $mode = $properties{mode} || $state->lookupValue('MODE') || 'restricted_horizontal'; if ($state->lookupValue('IN_MATH')) { my $attr = (defined $string) && $state->lookupValue('math_token_attributes_' . $string); my $usestring = ($attr && $$attr{replace}) || $string; @@ -47,7 +47,7 @@ sub Box { mode => $mode, ($attr ? %$attr : ()), %properties); } else { return LaTeXML::Core::Box->new($string, $font, $locator, $tokens, - mode => $mode, %properties); } } + mode => $mode, %properties); } } #====================================================================== # Box Object @@ -113,10 +113,10 @@ my %mode_abbrev = ( sub _stringify { my ($self) = @_; - my $type = ref $self; - my $mode = $$self{properties}{mode}; + my $type = ref $self; + my $mode = $$self{properties}{mode}; $type =~ s/^LaTeXML::Core:://; - $type .= '!'. ($mode_abbrev{$mode} || $mode) if $mode; + $type .= '!' . ($mode_abbrev{$mode} || $mode) if $mode; return $type; } sub stringify { @@ -235,12 +235,6 @@ sub getSize { unless (defined $$props{cwidth}) && (defined $$props{cheight}) && (defined $$props{cdepth}); - Debug("SIZE of $self" - . "\n preassigned: " . _showsize($$props{width}, $$props{height}, $$props{depth}) - . "\n calculated : " . _showsize($$props{cwidth}, $$props{cheight}, $$props{cdepth}) - . "\n w/options " . join(',', map { $_ . "=" . ToString($options{$_}); } sort keys %options) - . "\n =>: " . _showsize($$props{width} || $$props{cwidth}, $$props{height} || $$props{cheight}, $$props{depth} || $$props{cdepth}) - . "\n Of " . ToString($self)) if $LaTeXML::DEBUG{size}; return ($$props{width} || $$props{cwidth}, $$props{height} || $$props{cheight}, $$props{depth} || $$props{cdepth}, @@ -260,19 +254,25 @@ sub showSize { #omg # Fake computing the dimensions of strings (typically single chars). # Eventually, this needs to link into real font data +our @sizing_properties = (qw( + width height depth totalheight vattach layout + padtop padbottom padleft padright)); + sub computeSizeStore { my ($self, %options) = @_; no warnings 'recursion'; my $props = $self->getPropertiesRef; - $options{width} = $$props{width} if $$props{width}; - $options{height} = $$props{height} if $$props{height}; - $options{depth} = $$props{depth} if $$props{depth}; - $options{vattach} = $$props{vattach} if $$props{vattach}; - $options{layout} = $$props{layout} if $$props{layout}; + map { $options{$_} = $$props{$_} if defined $$props{$_}; } @sizing_properties; my ($w, $h, $d) = $self->computeSize(%options); $$props{cwidth} = $w unless defined $$props{cwidth}; $$props{cheight} = $h unless defined $$props{cheight}; $$props{cdepth} = $d unless defined $$props{cdepth}; + Debug("SIZE of $self" + . "\n preassigned: " . _showsize($$props{width}, $$props{height}, $$props{depth}) + . "\n calculated : " . _showsize($$props{cwidth}, $$props{cheight}, $$props{cdepth}) + . "\n w/options " . join(',', map { $_ . "=" . ToString($options{$_}); } sort keys %options) + . "\n =>: " . _showsize($$props{width} || $$props{cwidth}, $$props{height} || $$props{cheight}, $$props{depth} || $$props{cdepth}) + . "\n Of " . ToString($self)) if $LaTeXML::DEBUG{size}; return; } sub computeSize { From 2b2ed28987f5fd68acf34451562fef1860da1802 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Tue, 26 May 2026 12:24:22 -0400 Subject: [PATCH 21/31] Simplify simple underline/overline --- lib/LaTeXML/Engine/TeX_Math.pool.ltxml | 28 +++++++++----------------- lib/LaTeXML/resources/CSS/LaTeXML.css | 3 +++ t/fonts/marvosym.xml | 4 ++-- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/lib/LaTeXML/Engine/TeX_Math.pool.ltxml b/lib/LaTeXML/Engine/TeX_Math.pool.ltxml index 2f81ba7a4..b8bc3b680 100644 --- a/lib/LaTeXML/Engine/TeX_Math.pool.ltxml +++ b/lib/LaTeXML/Engine/TeX_Math.pool.ltxml @@ -300,7 +300,7 @@ sub cleanup_XMText { $document->replaceNode($m, map { $_->childNodes } $m->childNodes); } else { # Otherwise, wrap whatever it is in an XMText $document->wrapNodes('ltx:XMText', $m); } - } } } + } } } # And now we don't need the XMText any more. foreach my $attr ($textnode->attributes) { # Copy the child's attributes (should Merge!!) $table->setAttribute($attr->nodeName => $attr->getValue); } @@ -423,7 +423,7 @@ sub scriptHandler { my $c = (($op eq 'SUPERSCRIPT') ? '^' : '_'); Error('unexpected', $c, $stomach, "Script $c can only appear in math mode"); return Box($c, undef, undef, (($op eq 'SUPERSCRIPT') ? T_SUPER : T_SUB)); -} } + } } DefPrimitiveI(T_SUPER, undef, sub { scriptHandler($_[0], 'SUPERSCRIPT'); }); DefPrimitiveI(T_SUB, undef, sub { scriptHandler($_[0], 'SUBSCRIPT'); }); @@ -544,7 +544,7 @@ DefRewrite(xpath => 'descendant::ltx:Math[child::ltx:XMath[child::ltx:XMApp[' . elsif ($role eq 'FLOATSUBSCRIPT') { $document->insertElement('ltx:sub', $text); return; } - } } } } + } } } } # should never happen, but just in case: Info("rewrite", "footnotemark", "Failed to find floating node in: " . $math->toString(1)); $document->getNode->appendChild($math); @@ -819,7 +819,7 @@ sub augmentDelimiterProperties { if (exists $$entry{char}) { # replace the char content! if (my $char = $$entry{char}) { $delim->firstChild->setData($char); } } - } } + } } return; } # Useful for afterConstruct of delimiter sizing macros (eg. \bigl) @@ -952,24 +952,16 @@ DefMath('\lx@math@overline{}', UTF(0xAF), operator_role => 'OVERACCENT', operator_stretchy => 'true', name => 'overline', alias => '\overline'); DefConstructor('\lx@text@overline{}', - "#1", - enterHorizontal => 1, mode => 'restricted_horizontal', - sizer => sub { - my $text = $_[0]->getArg(1); - my ($w, $h, $d) = $text->getSize; - my $pad = Dimension($text->getFont->getEXHeight); # WAG - return ($w, $h->add($pad), $d); }); + "#1", + enterHorizontal => 1, mode => 'restricted_horizontal', + sizer => '#1', properties => { padtop => Dimension('2pt') }); DefMath('\lx@math@underline{}', UTF(0xAF), operator_role => 'UNDERACCENT', operator_stretchy => 'true', name => 'underline', alias => '\underline'); DefConstructor('\lx@text@underline{}', - "#1", - enterHorizontal => 1, mode => 'restricted_horizontal', - sizer => sub { - my $text = $_[0]->getArg(1); - my ($w, $h, $d) = $text->getSize; - my $pad = Dimension($text->getFont->getEXHeight); # WAG - return ($w, $h, $d->add($pad)); }); + "#1", + enterHorizontal => 1, mode => 'restricted_horizontal', + sizer => '#1', properties => { padbottom => Dimension('2pt') }); DefMath('\lx@math@overrightarrow{}', "\x{2192}", operator_role => 'OVERACCENT', operator_stretchy => 'true', name => 'overrightarrow', alias => '\overrightarrow'); diff --git a/lib/LaTeXML/resources/CSS/LaTeXML.css b/lib/LaTeXML/resources/CSS/LaTeXML.css index 4f4578337..758ff3d0c 100644 --- a/lib/LaTeXML/resources/CSS/LaTeXML.css +++ b/lib/LaTeXML/resources/CSS/LaTeXML.css @@ -442,6 +442,9 @@ span.ltx_framed { display:inline-block; text-indent:0; } /* avoid padding/ .ltx_rule { vertical-align: bottom; height: 0.4pt; width: 0.4pt; } +.ltx_underline { text-decoration: underline; } +.ltx_overline { text-decoration: overline; } + /*====================================================================== Misc */ /* .ltx_verbatim*/ diff --git a/t/fonts/marvosym.xml b/t/fonts/marvosym.xml index d902bc3ac..b0f38ec92 100644 --- a/t/fonts/marvosym.xml +++ b/t/fonts/marvosym.xml @@ -126,9 +126,9 @@ △╳, Ⓐ, Ⓟ, -Ⓟ, +Ⓟ, Ⓕ, -Ⓕ, +Ⓕ, \Ironing, \ironing, \IRONING, From ab6fe78bb6f4bb46cfa4c584aa548f5fbc4c84df Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Tue, 26 May 2026 12:25:24 -0400 Subject: [PATCH 22/31] Update deprecated --- lib/LaTeXML/Package/fancyvrb.sty.ltxml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/LaTeXML/Package/fancyvrb.sty.ltxml b/lib/LaTeXML/Package/fancyvrb.sty.ltxml index 69e3afdac..caee3477f 100644 --- a/lib/LaTeXML/Package/fancyvrb.sty.ltxml +++ b/lib/LaTeXML/Package/fancyvrb.sty.ltxml @@ -22,7 +22,7 @@ InputDefinitions('fancyvrb', type => 'sty', noltxml => 1); # Hack internals to add css class ltx_nowrap Let('\lx@save@FancyVerbFormatLine', '\FancyVerbFormatLine'); DefMacro('\FancyVerbFormatLine{}', - '\@ADDCLASS{ltx_verbatim}\lx@save@FancyVerbFormatLine{#1}'); + '\lx@add@cssclass{ltx_verbatim}\lx@save@FancyVerbFormatLine{#1}'); #********************************************************************** 1; From 55458d0beb6517ccc858bfbc180fe91a0b4f7314 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Tue, 26 May 2026 12:27:24 -0400 Subject: [PATCH 23/31] Add padding to items, equations --- lib/LaTeXML/Engine/TeX_Math.pool.ltxml | 5 +++- .../Engine/latex_constructs.pool.ltxml | 27 ++++++++++++------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/lib/LaTeXML/Engine/TeX_Math.pool.ltxml b/lib/LaTeXML/Engine/TeX_Math.pool.ltxml index b8bc3b680..fe64321c2 100644 --- a/lib/LaTeXML/Engine/TeX_Math.pool.ltxml +++ b/lib/LaTeXML/Engine/TeX_Math.pool.ltxml @@ -133,7 +133,10 @@ DefConstructorI('\lx@begin@display@math', undef, beforeDigest => sub { $_[0]->enterHorizontal; $_[0]->beginMode('display_math'); }, - properties => { mode => 'display_math' }, + properties => sub { + (mode => 'display_math', + padtop => LookupRegister('\abovedisplayskip'), + padbottom => LookupRegister('\belowdisplayskip')); }, captureBody => 1); DefConstructorI('\lx@end@display@math', undef, "", reversion => Tokens(T_MATH, T_MATH), diff --git a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml index 3f57de830..d9b8df62a 100644 --- a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml +++ b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml @@ -1368,7 +1368,11 @@ sub RefStepItemCounter { my %attr = (); my $sep = LookupDimension('\itemsep'); if (($n > 0) && $sep && ($sep->valueOf != LookupDimension('\lx@default@itemsep')->valueOf)) { - $attr{itemsep} = $sep; } + $attr{itemsep} = $sep; + # this SHOULD pad the item, but \item is ONLY the marker, not the item content! +## $attr{padtop} = $sep; +## $attr{height} = Dimension('1em'); + } if (defined $tag) { my @props = RefStepID($counter); if (IsEmpty($tag)) { # empty tag? @@ -1670,11 +1674,11 @@ DefConstructor('\trivlist@item@ OptionalUndigested', properties => sub { ($_[1] ? (tag => Digest(Expand($_[1]))) : ()); }); DefMacro('\@trivlist', '\relax', locked => 1); -DefRegister('\topsep' => Glue(0)); -DefRegister('\partopsep' => Glue(0)); -DefRegister('\lx@default@itemsep' => Glue(0)); -DefRegister('\itemsep' => Glue(0)); -DefRegister('\parsep' => Glue(0)); +DefRegister('\topsep' => Glue('8pt plus 2pt minus 4pt')); +DefRegister('\partopsep' => Glue('2pt plus 1pt minus 1pt')); +DefRegister('\lx@default@itemsep' => Glue('4pt plus 2pt minus 1pt')); +DefRegister('\itemsep' => Glue('4pt plus 2pt minus 1pt')); +DefRegister('\parsep' => Glue('4pt plus 2pt minus 1pt')); DefRegister('\@topsep' => Glue(0)); DefRegister('\@topsepadd' => Glue(0)); DefRegister('\@outerparskip' => Glue(0)); @@ -2024,6 +2028,9 @@ sub afterEquation { elsif ($whatsit) { $whatsit->setProperties(%{ LookupValue('EQUATIONROW_TAGS') }); } $$numbering{in_equation} = 0; + $whatsit->setProperties( + padtop => LookupRegister('\abovedisplayskip'), + padbottom => LookupRegister('\belowdisplayskip')) if $whatsit; return; } # My first inclination is to Lock {math}, but it is surprisingly common to redefine it in silly ways... So...? @@ -2969,7 +2976,7 @@ sub defineNewTheorem { T_CS('\let'), T_CS('\the' . $counter), T_CS('\@empty'), Invocation(T_CS('\lx@make@tags'), T_OTHER($thmset)), T_END); - } } + } } else { %ctr = RefStepCounter($thmset); } } my $title = DigestText(Tokens(T_BEGIN, @@ -3317,7 +3324,7 @@ sub arrange_panels_and_breaks { my $block = $document->wrapNodes('ltx:block', $prev_node, $child); push(@row_contents, [$block, 'ltx:block', $prev_width + $child_width]); pop(@all_panels); push(@all_panels, $block); - } } + } } else { # otherwise keep the last panel as-is, and append a sibling push(@row_contents, $prev_panel); @@ -4762,8 +4769,8 @@ DefConstructor('\lx@parbox[][Dimension] OptionalUndigested {Dimension} VBoxConte sizer => '#5', properties => sub { (width => $_[4], - vattach => translateAttachment($_[1]), - height => $_[2]); }, + vattach => translateAttachment($_[1]), + totalheight => $_[2]); }, mode => 'inline_internal_vertical', robust => 1, beforeDigest => sub { From e654a134d03a00609793fc05cef73560b4be6c7b Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Tue, 26 May 2026 12:29:06 -0400 Subject: [PATCH 24/31] Let space squeeze when laying out paragraphs; more informative debugging; support properties for padding, totalheight --- lib/LaTeXML/Common/Font.pm | 58 ++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/lib/LaTeXML/Common/Font.pm b/lib/LaTeXML/Common/Font.pm index ea1855d6e..d9a3c7ba2 100644 --- a/lib/LaTeXML/Common/Font.pm +++ b/lib/LaTeXML/Common/Font.pm @@ -646,6 +646,7 @@ sub computeBoxesSize { || $STATE->lookupDefinition(T_CS('\hsize')); $wrapwidth = $wrapwidth->valueOf if ref $wrapwidth; # Register or Dimension $wrapwidth = $wrapwidth->valueOf if ref $wrapwidth; } # still Dimension (Register) + my $maxwidth = $wrapwidth || 0; no warnings 'recursion'; my @boxes = grep { !(ref $_) || !$_->getProperty('isEmpty') } grep { !(ref $_) || $_->can('getSize'); } $boxes->unlist; @@ -659,6 +660,7 @@ sub computeBoxesSize { && (($box->getProperty('mode') || '') eq 'horizontal')) { my $width = $box->getProperty('width') || $wrapwidth; $width = $width->valueOf if ref $width; + $maxwidth = $width if $width && $width > $maxwidth; push(@lines, $self->computeBoxesSize_lines($width, $self->computeBoxesSize_words($box->unlist))); } else { @@ -666,17 +668,30 @@ sub computeBoxesSize { push(@lines, [$w, $h, $d]) if $w || $h || $d; } } } else { # Scan all boxes, collecting into "words", then (possibly) break into lines. + # Should get single line, if no $wrapwidth my @words = $self->computeBoxesSize_words(@boxes); @lines = $self->computeBoxesSize_lines($wrapwidth, @words); } # ---------------------------------------------------------------------- # Now, stack up the multiple lines my ($wd, $ht, $dp) = $self->computeBoxesSize_stack($vattach, @lines); - + $wd = $maxwidth if $wd && $maxwidth; # Set to wrapwidth, unless empty. + if (my $th = $options{totalheight}) { # divie up totalheight, if requested + $th = $th->valueOf; + my $diff = $th - $ht - $dp; + if ($diff > 0) { + if ($vattach eq 'bottom') { $ht += $diff; } + elsif ($vattach eq 'middle') { $ht += $diff/2; $dp += $diff/2; } + else { $dp += $diff; } } } Debug("Size boxes " . join(',', map { $_ . '=' . ToString($options{$_}); } sort keys %options) . "\n" . " Boxes: " . ToString($boxes) . "\n" . " Boxes: " . Stringify($boxes) . "\n" + . " Options:" . join(',',map { $_."=".ToString($options{$_}); } sort keys %options) . " Sizes: " . join("\n", map { _showsize(@$_); } @lines) . "\n" . " => " . _showsize($wd, $ht, $dp)) if $LaTeXML::DEBUG{'size-detailed'}; + $wd += $options{padleft}->valueOf if $options{padleft}; + $wd += $options{padright}->valueOf if $options{padright}; + $ht += $options{padtop}->valueOf if $options{padtop}; + $dp += $options{padbottom}->valueOf if $options{padbottom}; return (Dimension($wd), Dimension($ht), Dimension($dp)); } # Compute (w/guards) the size of a single box @@ -706,6 +721,7 @@ sub computeBoxesSize_words { no warnings 'recursion'; my ($self, @boxes) = @_; my @words = (); + my @word = (); my $prevbox; my $prevspace = 0; my $size = int($self->getSize || DEFSIZE() || 10); @@ -715,21 +731,22 @@ sub computeBoxesSize_words { # Check for possible line-break points if ((ref $box) && $box->getProperty('isBreak')) { if ($wd || $ht || $dp || ($prevspace > 0)) { - push(@words, [$prevspace, $wd, $ht, $dp]); - $wd = $ht = $dp = 0; $prevspace = -1; } + push(@words, [$prevspace, $wd, $ht, $dp, @word]); + $wd = $ht = $dp = 0; $prevspace = -1; @word = (); } else { $prevspace = -1; } } # Pernaps not "isSpace", but excluding struts, neg space, etc ??? elsif ((ref $box) && $box->getProperty('isSpace') && !$box->getProperty('isVerticalSpace')) { if ($wd || $ht || $dp || ($prevspace < 0)) { - push(@words, [$prevspace, $wd, $ht, $dp]); - $wd = $ht = $dp = 0; $prevspace = $w; } + push(@words, [$prevspace, $wd, $ht, $dp, @word]); + $wd = $ht = $dp = 0; $prevspace = $w; @word = (); } else { $prevspace += $w; } } else { # Else accumulate into "word" $wd += $w; $ht = max($ht, $h); $dp = max($dp, $d); + push(@word, $box); # Kern HACK for lists of individual Box's if ($prevbox && (ref $prevbox eq 'LaTeXML::Core::Box') && (ref $box eq 'LaTeXML::Core::Box')) { my $prevchar = substr($prevbox->getString || '', -1, 1); @@ -741,28 +758,32 @@ sub computeBoxesSize_words { $wd += $size * $kern; } } } $prevbox = $box; } - if ($wd || $ht || $dp || $prevspace) { # be sure to get last bit - push(@words, [$prevspace, $wd, $ht, $dp]); } + if ($wd || $ht || $dp || $prevspace || @word) { # be sure to get last bit + push(@words, [$prevspace, $wd, $ht, $dp, @word]); } return @words; } # do line breaking of words into lines, according to $wrapwidth (if), or explicit breaks. sub computeBoxesSize_lines { my ($self, $wrapwidth, @words) = @_; my @lines = (); + my @line = (); + my $fuzz = Dimension('1pt')->valueOf; + my $squeeze = ($wrapwidth ? 0.6 : 1.0); # Let spaces shrink in paragraph mode my ($wd, $ht, $dp) = (0, 0, 0); foreach my $item (@words) { - my ($space, $w, $h, $d) = @$item; + my ($space, $w, $h, $d, @word) = @$item; if ($space == -1) { - push(@lines, [$wd, $ht, $dp]) if $wd; - $wd = $w; $ht = $h; $dp = $d; } - elsif ((defined $wrapwidth) && ($wd + $space * 0.5 + $w > $wrapwidth)) { - push(@lines, [$wrapwidth || $wd, $ht, $dp]) if $wd; - $wd = $w; $ht = $h; $dp = $d; } + push(@lines, [$wd, $ht, $dp, @line]) if $wd; + $wd = $w; $ht = $h; $dp = $d; @line = @word; } + elsif ((defined $wrapwidth) && ($wd + $space * 0.5 + $w > $wrapwidth + $fuzz)) { + push(@lines, [$wd, $ht, $dp, @line]) if $wd; + $wd = $w; $ht = $h; $dp = $d; @line = @word; } else { - $wd += $space + $w; + $wd += $space*$squeeze + $w; $ht = max($ht, $h); - $dp = max($dp, $d); } } - push(@lines, [$wrapwidth || $wd, $ht, $dp]) if $wd || $ht || $dp; + $dp = max($dp, $d); + push(@line, @word); } } + push(@lines, [$wd, $ht, $dp, @line]) if $wd || $ht || $dp; return @lines; } # Sum up a stack of lines, determining w as max, and h & d according to $vattach. @@ -835,8 +856,9 @@ sub math_bearing { return $STATE->lookupDefinition($$mathbearingreg[abs($bearing)])->valueOf->spValue; } sub _showsize { - my ($wd, $ht, $dp) = @_; - return ($wd / $UNITY) . " x " . ($ht / $UNITY) . " + " . ($dp / $UNITY); } + my ($wd, $ht, $dp, @stuff) = @_; + return ($wd / $UNITY) . " x " . ($ht / $UNITY) . " + " . ($dp / $UNITY) + . (@stuff ? ' '.join('', map { ToString($_); } @stuff) : ''); } sub isSticky { my ($self) = @_; From f6309c6f8db37633472cd322967350899cfc2d88 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 28 May 2026 17:22:49 -0400 Subject: [PATCH 25/31] Bypass size computation if Box size completely specified --- lib/LaTeXML/Core/Box.pm | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/LaTeXML/Core/Box.pm b/lib/LaTeXML/Core/Box.pm index 87460186e..87087c629 100644 --- a/lib/LaTeXML/Core/Box.pm +++ b/lib/LaTeXML/Core/Box.pm @@ -263,7 +263,15 @@ sub computeSizeStore { no warnings 'recursion'; my $props = $self->getPropertiesRef; map { $options{$_} = $$props{$_} if defined $$props{$_}; } @sizing_properties; - my ($w, $h, $d) = $self->computeSize(%options); + my $w = $options{width}; + my $h = $options{height}; + my $d = $options{depth}; + if ((defined $w) && (defined $h) && (defined $d)) { + $w = Dimension($w) unless ref $w; + $h = Dimension($h) unless ref $h; + $d = Dimension($d) unless ref $d; } + else { + ($w, $h, $d) = $self->computeSize(%options); } $$props{cwidth} = $w unless defined $$props{cwidth}; $$props{cheight} = $h unless defined $$props{cheight}; $$props{cdepth} = $d unless defined $$props{cdepth}; From c56d7ec189b619ac3423a06a75b49acc3392f02c Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 28 May 2026 17:28:02 -0400 Subject: [PATCH 26/31] Make \vskip specified as pure height, no width, depth --- lib/LaTeXML/Engine/TeX_Glue.pool.ltxml | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/LaTeXML/Engine/TeX_Glue.pool.ltxml b/lib/LaTeXML/Engine/TeX_Glue.pool.ltxml index 50295cbf0..99a4962d2 100644 --- a/lib/LaTeXML/Engine/TeX_Glue.pool.ltxml +++ b/lib/LaTeXML/Engine/TeX_Glue.pool.ltxml @@ -84,21 +84,22 @@ DefConstructor('\hskip Glue', sub { reversion => sub { revertSkip(T_CS('\hskip'), $_[1]); }, attributeForm => sub { DimensionToSpaces($_[1]); }, enterHorizontal => 1, - properties => sub { (width => $_[1], isSpace => 1, isSkip => 1); }); + properties => sub { (width => $_[1], isSpace => 1, isSkip => 1); }); # We should be combining adjacent vskips (?) # And then add appropriate class/css for spacing to preceding element. DefConstructor('\vskip Glue', sub { - my ($document, $length, %props) = @_; - $length = $length->ptValue; - if ($length <= 0) { } # Or what!?!?!?! - elsif(($length < 4) && $document->isCloseable('ltx:p')) { - $document->closeElement('ltx:p'); } - elsif($document->isCloseable('ltx:para')) { - $document->closeElement('ltx:para'); } - return; }, + my ($document, $length, %props) = @_; + $length = $length->ptValue; + if ($length <= 0) { } # Or what!?!?!?! + elsif (($length < 4) && $document->isCloseable('ltx:p')) { + $document->closeElement('ltx:p'); } + elsif ($document->isCloseable('ltx:para')) { + $document->closeElement('ltx:para'); } + return; }, leaveHorizontal => 1, - properties => sub { (height => $_[1], isSpace => 1, isSkip => 1, isVerticalSpace => 1); }); + properties => sub { (height => $_[1], width => Dimension(0), depth => Dimension(0), + isSpace => 1, isSkip => 1, isVerticalSpace => 1); }); # Remove skip, if last on LIST DefPrimitiveI('\unskip', undef, sub { @@ -125,12 +126,12 @@ DefPrimitiveI('\hss', undef, undef, enterHorizontal => 1,); DefPrimitiveI('\hfilneg', undef, undef, enterHorizontal => 1); DefPrimitiveI('\hfil', undef, sub { - Box(' ', undef, undef, T_CS('\hfil'), isSpace => 1, isFill => 1); }, + Box(' ', undef, undef, T_CS('\hfil'), isSpace => 1, isFill => 1); }, enterHorizontal => 1); ### Box("\x{200B}", undef, undef, T_CS('\hfil'), isSpace => 1, isFill => 1); }); ### Box("\x{200A}", undef, undef, T_CS('\hfil'), isSpace => 1, isFill => 1); }); DefPrimitiveI('\hfill', undef, sub { - Box(' ', undef, undef, T_CS('\hfill'), isSpace => 1, isFill => 1); }, + Box(' ', undef, undef, T_CS('\hfill'), isSpace => 1, isFill => 1); }, enterHorizontal => 1); #### Box("\x{200B}", undef, undef, T_CS('\hfill'), isSpace => 1, isFill => 1); }); ### Box("\x{200A}", undef, undef, T_CS('\hfill'), isSpace => 1, isFill => 1); }); From 86cc33a531c2ad870767859190202717fdb538b2 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 28 May 2026 17:31:44 -0400 Subject: [PATCH 27/31] reindent graphics.sty --- lib/LaTeXML/Package/graphics.sty.ltxml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/LaTeXML/Package/graphics.sty.ltxml b/lib/LaTeXML/Package/graphics.sty.ltxml index f3878e1ea..45cb60871 100644 --- a/lib/LaTeXML/Package/graphics.sty.ltxml +++ b/lib/LaTeXML/Package/graphics.sty.ltxml @@ -63,9 +63,9 @@ DefParameterType('GraphixDimensions', sub { sub graphics_scaledbox_props { my ($box, $xscale, $yscale) = @_; my ($w, $h, $d) = $box->getSize; - my ($sw, $sh, $sd) = + my ($sw, $sh, $sd) = ($w && $w->multiply($xscale), $h && $h->multiply($yscale), $d && $d->multiply($yscale)); - my $H = $h && ($d ? $h->add($d) : $h); + my $H = $h && ($d ? $h->add($d) : $h); my $sH = $sh && ($sd ? $sh->add($sd) : $sh); return ( box => $box, @@ -78,7 +78,7 @@ sub graphics_scaledbox_props { depth => $sd, totalheight => $h && ($d ? $h->add($d) : $h)->multiply($yscale), xtranslate => $sw && $w && $sw->subtract($w)->multiply(+0.5), - ytranslate => $sH && $H && $sH->subtract($H)->multiply(-0.5) ); } + ytranslate => $sH && $H && $sH->subtract($H)->multiply(-0.5)); } sub graphics_scaledbox_insert { my ($document, %props) = @_; From 7229b26095a649d8bc97e7507e524299c378fe02 Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 28 May 2026 17:49:12 -0400 Subject: [PATCH 28/31] parbox doesn't indent paragraphs --- lib/LaTeXML/Engine/latex_constructs.pool.ltxml | 4 +++- lib/LaTeXML/resources/CSS/LaTeXML.css | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml index d9b8df62a..5df0b57ad 100644 --- a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml +++ b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml @@ -4756,7 +4756,9 @@ Let('\lx@parboxnewline', '\lx@newline'); # Obsolete, but in case still used # NOTE: There are 2 extra arguments (See LaTeX Companion, p.866) # for height and inner-pos. We're ignoring inner-pos, for now, though. DefMacro('\parbox[] [] [] {Dimension}{}', -'\lx@hidden@bgroup\hsize=#4\textwidth\hsize\columnwidth\hsize\ifx.#2.\lx@parbox[#1]{#4}{#5}\else\lx@parbox[#1][#2][#3]{#4}{#5}\fi\lx@hidden@egroup'); + '\lx@hidden@bgroup\hsize=#4\textwidth\hsize\columnwidth\hsize' + . '\parindent\z@\parskip\z@skip' + . '\ifx.#2.\lx@parbox[#1]{#4}{#5}\else\lx@parbox[#1][#2][#3]{#4}{#5}\fi\lx@hidden@egroup'); DefConstructor('\lx@parbox[][Dimension] OptionalUndigested {Dimension} VBoxContents', sub { my ($document, $attachment, $b, $c, $width, $body, %props) = @_; diff --git a/lib/LaTeXML/resources/CSS/LaTeXML.css b/lib/LaTeXML/resources/CSS/LaTeXML.css index 758ff3d0c..aeafa34fd 100644 --- a/lib/LaTeXML/resources/CSS/LaTeXML.css +++ b/lib/LaTeXML/resources/CSS/LaTeXML.css @@ -336,7 +336,7 @@ dl.ltx_description dl.ltx_description dd { margin-left:3em; } max-width:0em; text-align:right; } */ .ltx_parbox { - text-indent:0em; + text-indent:0em !important; display: inline-block; } /* NOTE that it is CRITICAL to put position:relative outside & absolute inside!! From 43d048e7571169c2809e317f27fcfed57d2ba4dd Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 28 May 2026 17:54:51 -0400 Subject: [PATCH 29/31] Improve itemization sizing --- .../Engine/latex_constructs.pool.ltxml | 59 +++++++++---------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml index 5df0b57ad..3fef2ca39 100644 --- a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml +++ b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml @@ -1031,22 +1031,22 @@ DefMacro('\@dbltoplist', ''); DefMacro('\@dbldeferlist', ''); DefMacro('\@startcolumn', ''); # Style parameters from Fig. C.3, p.182 -DefRegister('\paperheight' => Dimension('11in')); -DefRegister('\paperwidth' => Dimension('8.5in')); -DefRegister('\textheight' => Dimension('550pt')); -DefRegister('\textwidth' => Dimension('345pt')); -DefRegister('\topmargin' => Dimension(0)); -DefRegister('\headheight' => Dimension(0)); -DefRegister('\headsep' => Dimension(0)); -DefRegister('\footskip' => Dimension(0)); -DefRegister('\footheight' => Dimension(0)); -DefRegister('\evensidemargin' => Dimension(0)); -DefRegister('\oddsidemargin' => Dimension(0)); -DefRegister('\marginparwidth' => Dimension(0)); -DefRegister('\marginparsep' => Dimension(0)); -DefRegister('\columnwidth' => Dimension('6in')); -DefRegister('\linewidth' => Dimension('6in')); -DefRegister('\baselinestretch' => Dimension(0)); +DefRegister('\paperheight' => Dimension('11in')); +DefRegister('\paperwidth' => Dimension('8.5in')); +DefRegister('\textheight' => Dimension('550pt')); +DefRegister('\textwidth' => Dimension('345pt')); +DefRegister('\topmargin' => Dimension(0)); +DefRegister('\headheight' => Dimension(0)); +DefRegister('\headsep' => Dimension(0)); +DefRegister('\footskip' => Dimension(0)); +DefRegister('\footheight' => Dimension(0)); +DefRegister('\evensidemargin' => Dimension(0)); +DefRegister('\oddsidemargin' => Dimension(0)); +DefRegister('\marginparwidth' => Dimension(0)); +DefRegister('\marginparsep' => Dimension(0)); +DefRegister('\columnwidth' => Dimension('6in')); +DefRegister('\linewidth' => Dimension('6in')); +DefMacro('\baselinestretch' => '1'); #====================================================================== # C.5.4 The Title Page and Abstract @@ -1352,7 +1352,8 @@ sub beginItemize { ResetCounter($usecounter); } ## return RefStepCounter('@itemize' . $listpostfix); } return (RefStepCounter('@itemize' . $listpostfix), - counter => $usecounter, series => $series); } # So end can save counter value + counter => $usecounter, series => $series, # So end can save counter value + padtop => LookupDimension('\topsep')); } # These counters are ONLY used for id's of ALL the various itemize, enumerate, etc elements # Only create the 1st level (so that binding style can start numbering 'within' appropriately) @@ -1367,12 +1368,9 @@ sub RefStepItemCounter { AssignValue(itemization_items => $n + 1); my %attr = (); my $sep = LookupDimension('\itemsep'); - if (($n > 0) && $sep && ($sep->valueOf != LookupDimension('\lx@default@itemsep')->valueOf)) { - $attr{itemsep} = $sep; - # this SHOULD pad the item, but \item is ONLY the marker, not the item content! -## $attr{padtop} = $sep; -## $attr{height} = Dimension('1em'); - } + if (($n > 0) && $sep) { + if ($sep->valueOf != LookupDimension('\lx@default@itemsep')->valueOf) { + $attr{itemsep} = $sep; } } if (defined $tag) { my @props = RefStepID($counter); if (IsEmpty($tag)) { # empty tag? @@ -1449,12 +1447,11 @@ sub setEnumerationStyle { return; } # End paragraphs, like \par, but only if within an item's text -DefConstructorI('\preitem@par', undef, sub { - my ($document, %props) = @_; - if (!$props{inPreamble} && !$document->getNodeQName($document->getElement(), 'ltx:itemize')) { - $document->maybeCloseElement('ltx:p'); - $document->maybeCloseElement('ltx:para'); } }, - alias => '\par'); +# Also, add a vskip, between for sizing +DefMacro('\preitem@par', sub { + return ((LookupValue('itemization_items') || 0) > 0 + ? (T_CS('\par'), Invocation(T_CS('\vskip'), T_CS('\itemsep'))) + : ()); }); # id, but NO refnum (et.al) attributes on itemize \item ... # unless the optional tag argument was given! @@ -2976,7 +2973,7 @@ sub defineNewTheorem { T_CS('\let'), T_CS('\the' . $counter), T_CS('\@empty'), Invocation(T_CS('\lx@make@tags'), T_OTHER($thmset)), T_END); - } } + } } else { %ctr = RefStepCounter($thmset); } } my $title = DigestText(Tokens(T_BEGIN, @@ -3324,7 +3321,7 @@ sub arrange_panels_and_breaks { my $block = $document->wrapNodes('ltx:block', $prev_node, $child); push(@row_contents, [$block, 'ltx:block', $prev_width + $child_width]); pop(@all_panels); push(@all_panels, $block); - } } + } } else { # otherwise keep the last panel as-is, and append a sibling push(@row_contents, $prev_panel); From 988c737069cfd7060465eee8e48f1b4e2bcf2e6f Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 28 May 2026 17:57:59 -0400 Subject: [PATCH 30/31] Use dumped vspace macros, when possible; implement more of setspace.sty --- lib/LaTeXML/Engine/latex_base.pool.ltxml | 68 +++++++++---------- .../Engine/latex_constructs.pool.ltxml | 3 - lib/LaTeXML/Package/setspace.sty.ltxml | 9 +-- 3 files changed, 38 insertions(+), 42 deletions(-) diff --git a/lib/LaTeXML/Engine/latex_base.pool.ltxml b/lib/LaTeXML/Engine/latex_base.pool.ltxml index 8a1ebce94..834ea5bfa 100644 --- a/lib/LaTeXML/Engine/latex_base.pool.ltxml +++ b/lib/LaTeXML/Engine/latex_base.pool.ltxml @@ -32,8 +32,8 @@ use List::Util qw(min max); # C.0 Prelminaries & Shorthands #====================================================================== -Let('\@pushfilename','\lx@pushfilename'); -Let('\@popfilename', '\lx@popfilename'); +Let('\@pushfilename', '\lx@pushfilename'); +Let('\@popfilename', '\lx@popfilename'); DefMacroI('\@ehc', undef, "I can't help"); @@ -139,18 +139,18 @@ DefMacro('\@xviipt', '17.28'); DefMacro('\@xxpt', '20.74'); DefMacro('\@xxvpt', '24.88'); # LaTeX 209 stuff -DefMacro('\vpt', '\edef\f@size{\@vpt}\rm'); -DefMacro('\vipt', '\edef\f@size{\@vipt}\rm'); -DefMacro('\viipt', '\edef\f@size{\@viipt}\rm'); -DefMacro('\viiipt', '\edef\f@size{\@viiipt}\rm'); -DefMacro('\ixpt', '\edef\f@size{\@ixpt}\rm'); -DefMacro('\xpt', '\edef\f@size{\@xpt}\rm'); -DefMacro('\xipt', '\edef\f@size{\@xipt}\rm'); -DefMacro('\xiipt', '\edef\f@size{\@xiipt}\rm'); -DefMacro('\xivpt', '\edef\f@size{\@xivpt}\rm'); -DefMacro('\xviipt', '\edef\f@size{\@xviipt}\rm'); -DefMacro('\xxpt', '\edef\f@size{\@xxpt}\rm'); -DefMacro('\xxvpt', '\edef\f@size{\@xxvpt}\rm'); +DefMacro('\vpt', '\edef\f@size{\@vpt}\rm'); +DefMacro('\vipt', '\edef\f@size{\@vipt}\rm'); +DefMacro('\viipt', '\edef\f@size{\@viipt}\rm'); +DefMacro('\viiipt', '\edef\f@size{\@viiipt}\rm'); +DefMacro('\ixpt', '\edef\f@size{\@ixpt}\rm'); +DefMacro('\xpt', '\edef\f@size{\@xpt}\rm'); +DefMacro('\xipt', '\edef\f@size{\@xipt}\rm'); +DefMacro('\xiipt', '\edef\f@size{\@xiipt}\rm'); +DefMacro('\xivpt', '\edef\f@size{\@xivpt}\rm'); +DefMacro('\xviipt', '\edef\f@size{\@xviipt}\rm'); +DefMacro('\xxpt', '\edef\f@size{\@xxpt}\rm'); +DefMacro('\xxvpt', '\edef\f@size{\@xxvpt}\rm'); #********************************************************************** # Basic \documentclass & \documentstyle @@ -268,8 +268,8 @@ DefMacro('\@restorepar', '\def\par{\@par}'); NewCounter('footnote'); DefMacroI('\thefootnote', undef, '\arabic{footnote}'); NewCounter('mpfootnote'); -DefMacroI('\thempfn', undef, '\thefootnote'); -DefMacroI('\thempfootnote', undef, '\arabic{mpfootnote}'); +DefMacroI('\thempfn', undef, '\thefootnote'); +DefMacroI('\thempfootnote', undef, '\arabic{mpfootnote}'); DefRegister('\footnotesep' => Dimension(0)); #====================================================================== # C.3.4 Accents and Special Symbols @@ -291,9 +291,9 @@ DefMacroI('\appendixesname', undef, 'Appendixes'); # C.4.3 Table of Contents #====================================================================== # Insert stubs that will be filled in during post processing. -DefMacroI('\contentsname', undef, 'Contents'); +DefMacroI('\contentsname', undef, 'Contents'); DefMacroI('\listfigurename', undef, 'List of Figures'); -DefMacroI('\listtablename', undef, 'List of Tables'); +DefMacroI('\listtablename', undef, 'List of Tables'); #====================================================================== # C.4.4 Style registers #====================================================================== @@ -357,11 +357,10 @@ DefMacro('\subparagraphmark{}', Tokens()); DefMacro('\@tabacckludge {}', '\csname\string#1\endcsname'); DefPrimitive('\DeclareTextAccent DefToken {}{}', sub { - ignoredDefinition('DeclareTextAccent', $_[1]); }); + ignoredDefinition('DeclareTextAccent', $_[1]); }); DefPrimitive('\DeclareTextAccentDefault{}{}', sub { ignoredDefinition('DeclareTextAccentDefault', $_[1]); }); - DefPrimitive('\DeclareTextComposite{}{}{}{}', sub { ignoredDefinition('DeclareTextComposite', $_[1]); }); DefPrimitive('\DeclareTextCompositeCommand{}{}{}{}', @@ -401,17 +400,17 @@ DefMacroI('\floatpagefraction', undef, "0.25"); NewCounter('dbltopnumber'); DefMacroI('\dbltopfraction', undef, "0.7"); DefMacroI('\dblfloatpagefraction', undef, "0.25"); -DefRegister('\floatsep' => Glue('12.0pt plus 2.0pt minus 2.0pt')); -DefRegister('\textfloatsep' => Glue('20.0pt plus 2.0pt minus 4.0pt')); -DefRegister('\intextsep' => Glue('12.0pt plus 2.0pt minus 2.0pt')); -DefRegister('\dblfloatsep' => Glue('12.0pt plus 2.0pt minus 2.0pt')); -DefRegister('\dbltextfloatsep' => Glue('20.0pt plus 2.0pt minus 4.0pt')); -DefRegister('\@fptop' => Glue(0)); -DefRegister('\@fpsep' => Glue(0)); -DefRegister('\@fpbot' => Glue(0)); -DefRegister('\@dblfptop' => Glue(0)); -DefRegister('\@dblfpsep' => Glue(0)); -DefRegister('\@dblfpbot' => Glue(0)); +DefRegister('\floatsep' => Glue('12.0pt plus 2.0pt minus 2.0pt')); +DefRegister('\textfloatsep' => Glue('20.0pt plus 2.0pt minus 4.0pt')); +DefRegister('\intextsep' => Glue('12.0pt plus 2.0pt minus 2.0pt')); +DefRegister('\dblfloatsep' => Glue('12.0pt plus 2.0pt minus 2.0pt')); +DefRegister('\dbltextfloatsep' => Glue('20.0pt plus 2.0pt minus 4.0pt')); +DefRegister('\@fptop' => Glue(0)); +DefRegister('\@fpsep' => Glue(0)); +DefRegister('\@fpbot' => Glue(0)); +DefRegister('\@dblfptop' => Glue(0)); +DefRegister('\@dblfpsep' => Glue(0)); +DefRegister('\@dblfpbot' => Glue(0)); Let('\topfigrule', '\relax'); Let('\botfigrule', '\relax'); Let('\dblfigrule', '\relax'); @@ -485,7 +484,6 @@ RawTeX(<<'EOL'); \DeclareRobustCommand\usebox[1]{\leavevmode\copy #1\relax} EOL - #********************************************************************** # C.14 Pictures and Color #********************************************************************** @@ -597,7 +595,6 @@ EoTeX #====================================================================== - RawTeX(<<'EOTeX'); \chardef\@xxxii=32 \mathchardef\@Mi=10001 @@ -802,8 +799,9 @@ RawTeX(<<'EOTeX'); \def\glb@settings{}% EOTeX - - +DefPrimitive('\addvspace {}', undef); +DefPrimitive('\addpenalty {}', undef); +DefPrimitiveI('\@endparenv'); #====================================================================== DefMacroI('\loggingall', undef, Tokens()); diff --git a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml index 3fef2ca39..0c2e2cb2b 100644 --- a/lib/LaTeXML/Engine/latex_constructs.pool.ltxml +++ b/lib/LaTeXML/Engine/latex_constructs.pool.ltxml @@ -4647,9 +4647,6 @@ DefPrimitive('\hspace OptionalMatch:* {Dimension}', sub { width => $length, isSpace => 1); }); DefMacro('\vspace OptionalMatch:* {}', '\vskip #2\relax'); -DefPrimitive('\addvspace {}', undef); -DefPrimitive('\addpenalty {}', undef); -DefPrimitiveI('\@endparenv'); # \hfill, \vfill diff --git a/lib/LaTeXML/Package/setspace.sty.ltxml b/lib/LaTeXML/Package/setspace.sty.ltxml index 54c5a61f5..bcf208c12 100644 --- a/lib/LaTeXML/Package/setspace.sty.ltxml +++ b/lib/LaTeXML/Package/setspace.sty.ltxml @@ -26,10 +26,11 @@ DefEnvironment("{onehalfspace}", "#body"); DefEnvironment("{doublespace}", "#body"); DefEnvironment("{spacing}{}", "#body"); -DefMacroI('\setstretch', '{}', ''); -DefMacroI('\SetSinglespace', '{}', ''); -DefMacroI('\setdisplayskipstretch', '{}', ''); -DefMacroI('\restore@spacing', undef, ''); +DefMacro('\setspace@singlespace', '1'); +DefMacro('\setstretch{}', '\def\baselinestretch{#1}'); +DefMacro('\SetSinglespace{}', '\def\setspace@singlespace{#1}'); +DefMacro('\setdisplayskipstretch{}', ''); +DefMacro('\restore@spacing', ''); #********************************************************************** 1; From 2d3c6db31314277a34e6c0776b7a63453331751e Mon Sep 17 00:00:00 2001 From: Bruce Miller Date: Thu, 28 May 2026 18:12:44 -0400 Subject: [PATCH 31/31] Set svg size in em units (viewbox will scale to fit that size), then set foreignObject fontsize which WILL get rescaled by svg to match browser's current fontsize (surprisingly); update xy test case accordingly --- lib/LaTeXML/Engine/TeX_Box.pool.ltxml | 5 +-- lib/LaTeXML/Package/pgfsys-latexml.def.ltxml | 7 +++- lib/LaTeXML/Package/xy.tex.ltxml | 42 +++++++++---------- t/graphics/xytest.xml | 44 ++++++++++---------- 4 files changed, 50 insertions(+), 48 deletions(-) diff --git a/lib/LaTeXML/Engine/TeX_Box.pool.ltxml b/lib/LaTeXML/Engine/TeX_Box.pool.ltxml index 11fa3ab1d..e7eaaea47 100644 --- a/lib/LaTeXML/Engine/TeX_Box.pool.ltxml +++ b/lib/LaTeXML/Engine/TeX_Box.pool.ltxml @@ -385,7 +385,8 @@ Tag('svg:foreignObject', autoOpen => 1, autoClose => 1, $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;'); + . '--ltx-fo-depth:' . $d->emValue(undef, $f) . 'em;' + . 'font-size:' . $f->getSize . 'pt;'); } }); sub isVAttached { @@ -418,8 +419,6 @@ 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}) { - $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); $document->absorb($contents); diff --git a/lib/LaTeXML/Package/pgfsys-latexml.def.ltxml b/lib/LaTeXML/Package/pgfsys-latexml.def.ltxml index 6d44b4072..8ab9a5db6 100644 --- a/lib/LaTeXML/Package/pgfsys-latexml.def.ltxml +++ b/lib/LaTeXML/Package/pgfsys-latexml.def.ltxml @@ -84,10 +84,13 @@ DefConstructor('\lxSVG@insertpicture{}', sub { $document->absorb($content); $document->closeElement('svg:g'); } else { + my $font = $props{_font}; $document->openElement('ltx:picture'); $document->openElement('svg:svg', version => "1.1", - width => $props{pxwidth}, height => $props{pxheight}, style => $props{style}, + width => $props{width}->emValue(2, $font) . 'em', + height => $props{height}->emValue(2, $font) . 'em', + style => $props{style}, viewBox => "$props{minx} $props{miny} $props{pxwidth} $props{pxheight}", overflow => "visible"); my $x0 = -(0 + $props{minx}); @@ -134,6 +137,8 @@ DefConstructor('\lxSVG@insertpicture{}', sub { $whatsit->setProperty(pxwidth => $w); $whatsit->setProperty(pxheight => $h); $whatsit->setProperty(style => "vertical-align:" . $base . "px") if $base; + $whatsit->setProperty(_font => $whatsit->getFont); + # or tikz macro (see corescopes) return; }, # \pgfpicture seems to make a 0 sized box, which throws off our postprocessor diff --git a/lib/LaTeXML/Package/xy.tex.ltxml b/lib/LaTeXML/Package/xy.tex.ltxml index cb1105ec2..410c44ff2 100644 --- a/lib/LaTeXML/Package/xy.tex.ltxml +++ b/lib/LaTeXML/Package/xy.tex.ltxml @@ -93,20 +93,18 @@ DefConstructor('\lx@xy@svgnested Digested', return (width => $w, height => $y1, depth => $y0->negate, transform => $transform); }); DefConstructor('\lx@xy@svg Digested', sub { -# "" -# . "" -# . "#1" -# . "", - my ($document,$content,%props) = @_; + my ($document, $content, %props) = @_; + my $font = $props{_font}; $document->openElement('ltx:picture'); $document->openElement('svg:svg', - version => "1.1", overflow => "visible", - width => $props{pxwidth}, height => $props{pxheight}, style => $props{style}, - viewBox => "$props{minx} $props{miny} $props{pxwidth} $props{pxheight}"); - $document->openElement('svg:g',transform => $props{transform}, _scopebegin => 1); + version => "1.1", overflow => "visible", + width => $props{width}->emValue(2, $font) . 'em', + height => $props{height}->emValue(2, $font) . 'em', + style => $props{style}, + viewBox => "$props{minx} $props{miny} $props{pxwidth} $props{pxheight}"); + $document->openElement('svg:g', transform => $props{transform}, _scopebegin => 1); addSVGDebuggingBox($document, - $props{x},$props{y},$props{width},$props{height},'#FF00FF') + $props{x}, $props{y}, $props{width}, $props{height}, '#FF00FF') if $LaTeXML::DEBUG{svg}; $document->absorb($content); $document->closeElement('svg:g'); @@ -118,26 +116,26 @@ DefConstructor('\lx@xy@svg Digested', sub { || [Dimension(0), Dimension(0), Dimension(0), Dimension(0)] }; Debug("XY: " . ToString($x0) . ' ' . ToString($y0) . ' ' . ToString($x1) . ' ' . ToString($y1)) if $LaTeXML::DEBUG{svg_verbose}; - my $w = $x1->subtract($x0); - my $h = $y1->subtract($y0); - if($w->valueOf < 0){ # Rarely, the range hasn't actually been set?!? - $x0 = $x1 = Dimension(0); } - if($h->valueOf < 0){ - $y0 = $y1 = Dimension(0); } + my $w = $x1->subtract($x0); + my $h = $y1->subtract($y0); + if ($w->valueOf < 0) { # Rarely, the range hasn't actually been set?!? + $x0 = $x1 = Dimension(0); } + if ($h->valueOf < 0) { + $y0 = $y1 = Dimension(0); } my $x = $x0->negate; my $y = $y1->subtract($y0); my $minx = $x->pxValue; my $miny = $y0->negate->pxValue; my $transform = "matrix(1 0 0 -1 " . $x->pxValue . ' ' . $y->pxValue . ')'; - my $style = ($miny ? "vertical-align:".(-$miny)."px" : undef); - my $pxwidth = max($w->pxValue,1); - my $pxheight = max($h->pxValue,1); + my $style = ($miny ? "vertical-align:" . (-$miny) . "px" : undef); + my $pxwidth = max($w->pxValue, 1); + my $pxheight = max($h->pxValue, 1); Debug("XY size: " . ToString($w) . ' x ' . ToString($h) . ' + ' . 0 . ' @ ' . ToString($x) . ' x ' . ToString($y)) if $LaTeXML::DEBUG{svg_verbose}; return (x => $x0, y => $y0, width => $w, height => $h, - pxwidth => $pxwidth, pxheight => $pxheight, - minx => $minx, miny => $miny, style => $style, + pxwidth => $pxwidth, pxheight => $pxheight, + minx => $minx, miny => $miny, style => $style, transform => $transform); }); DefPrimitive('\lx@xy@capturerange', sub { diff --git a/t/graphics/xytest.xml b/t/graphics/xytest.xml index b7cf6c016..05c3a0d63 100644 --- a/t/graphics/xytest.xml +++ b/t/graphics/xytest.xml @@ -8,17 +8,17 @@ - + - + A - + B @@ -34,17 +34,17 @@ - + - + U - + y @@ -63,7 +63,7 @@ - + x @@ -75,7 +75,7 @@ - + @@ -91,7 +91,7 @@ - + q @@ -105,7 +105,7 @@ - + p @@ -118,7 +118,7 @@ - + X @@ -126,7 +126,7 @@ - + f @@ -139,7 +139,7 @@ - + Y @@ -147,7 +147,7 @@ - + g @@ -160,7 +160,7 @@ - + Z @@ -179,10 +179,10 @@ = x - + - + A @@ -210,21 +210,21 @@ - + B - + C - D + D @@ -247,13 +247,13 @@ [ - + - +
[Tabular One]
Lorem ipsum dolor sit amet, consectetur adipisicing elit
Cell long text with predefined width
Cell long…
- - Second multilined + + Second multilined - - column head + + column head
Second multilined
column head
Cell …
P-column
and another one
Some long text in the first column
bbb
aaa
and some long text in the second column
Note that the coloured rules in all columns stretch to accomodate + + Note that the coloured rules in all columns stretch to accomodate large entries in one column. - - bbb + + bbb @@ -179,11 +179,11 @@ large entries in one column.
Note that the coloured rules in all columns stretch to accomodate large entries in one column.
Depending on your driver you may get unsightly gaps or lines + + Depending on your driver you may get unsightly gaps or lines where the ‘screens’ used to produce different shapes interact badly. You may want to cause adjacent panels of the same colour by specifying a larger overhang @@ -209,11 +209,11 @@ or by adding some negative space (in a ”“noalign” between rows.
Depending on your driver you may get unsightly gaps or lines where the ‘screens’ used to produce different shapes interact badly. You may want to cause adjacent panels of the same colour by specifying a larger overhang @@ -209,11 +209,11 @@ or by adding some negative space (in a ”“noalign” between rows.
Bad year.
Light trading due to a heavy winter.
No gnus was very good gnus this year.
a
b
c
a a a a a a a a a a
Stone
C14 Dates
2150 + + 2150 ± @@ -488,12 +501,12 @@ the whales Save the whale Save the × 21 - - Hammerstone + + Hammerstone — - - — + + —
2150 ± @@ -488,12 +501,12 @@ the whales Save the whale Save the × 21
Hammerstone
—
1990 + + 1990 ± @@ -546,17 +559,30 @@ the whales Save the whale Save the × 8 - - — + + — Fired clay - - — + + —
1990 ± @@ -546,17 +559,30 @@ the whales Save the whale Save the × 8
Quartzite-lump