Sponsoring The Perl Toolchain Summit 2025: Help make this important event another success Learn more

# /=====================================================================\ #
# | LaTeXML::Post::MathML::Presentation | #
# | MathML generator for LaTeXML | #
# |=====================================================================| #
# | Part of LaTeXML: | #
# | Public domain software, produced as part of work done by the | #
# | United States Government & not subject to copyright in the US. | #
# |---------------------------------------------------------------------| #
# | Bruce Miller <bruce.miller@nist.gov> #_# | #
# \=========================================================ooo==U==ooo=/ #
use strict;
sub preprocess {
my ($self, $doc, @maths) = @_;
$self->SUPER::preprocess($doc, @maths);
if ($$self{linelength}) { # If we're doing linebreaking...
$self->preprocess_linebreaking($doc, @maths); }
return; }
# This would be the non-linebreaking version
sub convertNode_simple {
my ($self, $doc, $xmath, $style) = @_;
return $self->pmml_top($xmath, $style); }
# Convert a node and compute it's linebroken layout
sub convertNode_linebreak {
my ($self, $doc, $xmath, $style) = @_;
my $breaker = $$self{linebreaker};
if (!$breaker) {
$breaker = $$self{linebreaker} = LaTeXML::Post::MathML::Linebreaker->new(); }
my $pmml = $self->convertNode_simple($doc, $xmath, $style);
my $layout = $breaker->bestFitToWidth($xmath, $pmml, $$self{linelength}, 1);
if ($$layout{hasbreak}) { # YES it did linebreak!
$pmml = $breaker->applyLayout($pmml, $layout); }
return ($pmml, $$layout{hasbreak}); }
sub convertNode {
my ($self, $doc, $xmath) = @_;
my $style = (($xmath->parentNode->getAttribute('mode') || 'inline') eq 'display'
? 'display' : 'text');
my $id = $xmath->parentNode->getAttribute('xml:id');
# If this node has already been pre-converted
my $pmml;
if ($pmml = $id && $$doc{converted_pmml_cache}{$id}) { }
# A straight displayed Math will have been handled by preprocess_linebreaking (below),
# and, if it needed line-breaking, will have generated a MathFork/MathBranch.
# Other math, in the non-semantic side of a MathFork, may want to line break here as well.
# It presumably will NOT be display style(?)
# NEXT better strategy will be to scan columns of MathBranches to establish desired line length?
elsif ($$self{linelength} # If line breaking
&& ($doc->findnodes('ancestor::ltx:MathBranch', $xmath)) # In formatted side of MathFork?
# But ONLY if last column!! (until we can adapt LineBreaker!)
&& !$doc->findnodes('parent::ltx:Math/parent::ltx:td/following-sibling::ltx:td', $xmath)) {
my ($pmmlb, $broke) = $self->convertNode_linebreak($doc, $xmath, $style);
$pmml = $pmmlb; }
else {
$pmml = $self->convertNode_simple($doc, $xmath, $style); }
return { processor => $self, xml => $pmml, mimetype => 'application/mathml-presentation+xml' }; }
sub rawIDSuffix {
return '.pmml'; }
sub associateNodeHook {
my ($self, $node, $sourcenode) = @_;
# TODO: Shouldn't we have a single getQName shared for the entire latexml codebase
# in LaTeXML::Common or LaTeXML::Util ?
my $name = LaTeXML::Post::MathML::getQName($node);
if ($name =~ /^m:(?:mi|mo|mn)$/) {
if (my $href = $sourcenode->getAttribute('href')) {
if (ref $node eq 'ARRAY') {
$$node[1]{href} = $href; }
else {
$node->setAttribute('href', $href); } }
if (my $title = $sourcenode->getAttribute('title')) {
if (ref $node eq 'ARRAY') {
$$node[1]{title} = $title; }
else {
$node->setAttribute('title', $title); } } }
return; }
#================================================================================
# Presentation MathML with Line breaking
# Not at all sure how this will integrate with Parallel markup...
# Any displayed formula is a candidate for line-breaking.
# If it is not already in a MathFork, and needs line-breaking,
# then we ought to wrap in a MathFork, so as to preserve the
# slightly "semantically meaningful" form.
# If we're mangling the document structure in this way,
# it needs to be done before the main scan-all-math's loop,
# since it moves the maths around.
# However, since we also have to check whether it NEEDS line breaking beforehand,
# we might as well linebreak & store that line-broken result alongside.
# [it will get stored WITHOUT an XMath expression, though, so we won't be asked to redo it]
# convertNode will be called later on the main fork (unbroken).
# Also, other subexpressions inside MathFork/MathBranch that were created by
# the usual means (bindings for eqnarray, or whatever) will still need to
# be converted (convertNode).
# And in fact they also should be line-broken -- we just don't know the width!!
sub preprocess_linebreaking {
my ($self, $doc, @maths) = @_;
# Rewrap every displayed ltx:Math in an ltx:MathFork (if it isn't ALREADY in a MathFork).
# This is so that we can preserve the "more semantic" non-linebroken form as the main branch.
foreach my $math (@maths) {
my $mode = $math->getAttribute('mode') || 'inline';
next unless $mode eq 'display'; # SKIP if not in display mode?
my $style = ($mode eq 'display' ? 'display' : 'text');
# If already has in a MathBranch, we can't really know if, or how wide, to line break!?!?!
next if $doc->findnodes('ancestor::ltx:MathFork', $math); # SKIP if already in a branch?
# Now let's do the layout & see if it actually needs line breaks!
# next if $math isn't really so wide ..
my $id = $math->getAttribute('xml:id');
my $xmath = $doc->findnode('ltx:XMath', $math);
my ($pmml, $broke) = $self->convertNode_linebreak($doc, $xmath, $style);
if ($broke) { # YES it did linebreak!
# Replace the Math node with a MathFork that contains the Math node.
# And a MathBranch that ONLY contains the line-broken pmml.
# That branch won't get other parallel markup,
# but the main, more semantic(?) one, will and will get the unbroken pmml (?), as well.
my $p = $math->parentNode;
$id = $id . ".mbr" if $id;
$doc->replaceNode($math, ['ltx:MathFork', {}, $math,
['ltx:MathBranch', {},
['ltx:Math', { 'xml:id' => $id },
$self->outerWrapper($doc, $xmath, $pmml)]]]);
# Now,RE-mark, since the insertion removed internal attributes!
$doc->markXMNodeVisibility; }
# cache the converted pmml?
# But note that applyLayout MAY have MODIFIED the orignal $pmml, so it may have linebreaks!
# but then, it will have a modified id, as well!!! (.mbr appended)
if ($id) {
$$doc{converted_pmml_cache}{$id} = $pmml; }
}
return; }
#================================================================================
1;