1
# /=====================================================================\ #
2
# | LaTeXML::Util::MathMLLinebreaker | #
3
# | MathML generator for LaTeXML | #
4
# |=====================================================================| #
5
# | Part of LaTeXML: | #
6
# | Public domain software, produced as part of work done by the | #
7
# | United States Government & not subject to copyright in the US. | #
8
# |---------------------------------------------------------------------| #
9
# | Bruce Miller <bruce.miller@nist.gov> #_# | #
10
# | http://dlmf.nist.gov/LaTeXML/ (o o) | #
11
# \=========================================================ooo==U==ooo=/ #
13
package LaTeXML::Util::MathMLLinebreaker;
14
#======================================================================
15
# General MathML Line-Breaking Strategy.
17
# (1) Preparations: if top-level has trailing punctuation,
18
# remove it to be added back, later.
20
# (2) Find all layouts that fit within a specified width.
21
# This is done top-down by finding possible breaks within the current
22
# node, along with (recursively) the possible layouts for children.
23
# The pattern of possible breaks and which children are allowed to break
24
# depends on the tag of the current node; eg. sub/superscripts don't break.
25
# [handlers for each tag are defined at end of file]
27
# Each combination of these breaks is then considered by
28
# computing the effective size & penalty.
29
# The penalty is generally determined by the number of breaks,
30
# but possibly weighted.
32
# These layouts are sorted in increasing width, and increasing penalty
33
# for a given width. Layouts which are too wide, or have higher penalty
34
# than the previous layout are pruned.
36
# (3) Apply the breaks specified by the best layout.
37
# The last layout in a list of layouts will be the best in the sense
38
# that it is the longest layout not longer than the target width,
39
# and has least penalty of its width.
40
#======================================================================
41
# NOTE This takes the array form for the MathML, before it has been
42
# converted to a proper DOM tree.
46
#######################################################################
48
#######################################################################
50
our $NOBREAK = 99999999; # penalty=$NOBREAK means don't break at all.
51
our $POORBREAK_FACTOR= 10; # to make breaks less desirable.
52
our $BADBREAK_FACTOR = 100; # to make breaks much less desirable.
53
our $CONVERSION_FACTOR = 2; # to make breaks at converted ops less desirable
55
# TODO: Integrate default operator dictionary, and recognize attributes
56
# TODO: all addops, relops,
57
# TODO: mult ops, but less desirable
58
sub UTF { pack('U',$_[0]); }
60
our %BREAKOPS = map(($_=>1),
62
"+","-",UTF(0xB1),"\x{2213}", # pm, mp
64
our %RELATIONOPS = map(($_=>1),
66
"=", "<",">", "\x{2264}","\x{2265}","\x{2260}","\x{226A}",
67
"\x{2261}","\x{223C}","\x{2243}","\x{224D}","\x{2248}","\x{2260}","\x{221D}",
69
our %CONVERTOPS = ("\x{2062}"=>UTF(0xD7), # Invisible (discretionary) times
71
binmode(STDOUT,":utf8") if $DEBUG;
73
#######################################################################
75
#######################################################################
79
my $self = bless {},$class;
83
my($self,$math,$mml,$width,$displaystyle)=@_;
84
# Check for end punctuation; Remove it, if found.
86
local $LaTeXML::MathMLLineBreaker::MATH = $math;
87
local $LaTeXML::MathMLLineBreaker::PUNCT = undef;
88
if((nodeName($mml) eq 'm:mrow') && (scalar(@n=nodeChildren($mml))==2)
89
&& (nodeName($n[1]) eq 'm:mo') && (textContent($n[1]) =~ /^[\.\,\;]$/)){
91
$LaTeXML::MathMLLineBreaker::PUNCT = $n[1]; }
93
# Compute the possible layouts
94
print STDERR "Starting layout of $mml\n" if $DEBUG;
95
my $layouts = layout($mml,$width,0,$displaystyle,0,1);
97
print STDERR "Got ".scalar(@$layouts)." layouts:\n";
98
map(showLayout($_),@$layouts); }
100
# Apply the best layout
101
my $best = $$layouts[-1];
102
# Is there a case where $best is so bad we shouldn't even apply it?
104
# If nobody has yet eaten the punctuation, add it back on.
105
if($LaTeXML::MathMLLineBreaker::PUNCT){
106
$mml = ['m:mrow',{},$mml,$LaTeXML::MathMLLineBreaker::PUNCT]; }
108
Warn("Got width $$best{width} > $width") if $$best{width} > $width+1;
113
my $p = $LaTeXML::MathMLLineBreaker::MATH;
114
while($p && !$p->hasAttribute('xml:id')){
116
my $id = $p && $p->getAttribute('xml:id')||'?';
117
my $nm = $p && $p->getAttribute('refnum')||'';
118
print STDERR "Warning in MathMLLinebreaker for math in $nm (id=$id):\n $message\n"; }
121
#######################################################################
122
# Apply the breaks described in a layout to the MathML.
123
#######################################################################
124
# Modify the mathml to incorporate a set of breaks.
126
# This strategy uses mtable.
127
# NOTE: There's an alignment issue.
128
# In principle, a row could be broken that resides within another row.
129
# In such a case, you'd want the table to align the 1st row's baseline to the material
130
# on the left, but the material following should align to the last row's baseline!!!
131
# MathML spec doesn't give any way of saying that!
132
# So, currently, we've got code in asRow that forbids a break within anything
133
# but the _LAST_ item within a line.
136
return unless $$layout{hasbreak};
137
# Do children first, so if there is punctuation & last child breaks, it can take up the punct.
138
if($$layout{children}){
139
my @children_layout = @{$$layout{children}};
140
my $lastchild = pop(@children_layout);
141
{ local $LaTeXML::MathMLLineBreaker::PUNCT = undef; # Hide from all but last child
142
map(applyLayout($_), @children_layout); }
143
# Now, do last child; Maybe it will absorb the punctuation!
144
applyLayout($lastchild); }
145
# Now break up the current level.
146
if(my $breakset = $$layout{breakset}){
147
# print "Applying ".layoutDescriptor($layout)."\n";
148
my $node = $$layout{node};
149
my @children = nodeChildren($node);
150
# If this is a fenced row, we've got to manually fixup the fence size!
151
if(nodeName($node) eq 'm:mrow'){
152
if((nodeName($children[0]) eq 'm:mo')
153
&& (textContent($children[0]) =~ /[\(\)\[\]\{\}]/)){
154
$children[0][1]{mathsize}=$$layout{rowheight}."em"; }
155
if((nodeName($children[$#children]) eq 'm:mo')
156
&& (textContent($children[$#children]) =~ /[\(\)\[\]\{\}]/)){
157
$children[$#children][1]{mathsize}=$$layout{rowheight}."em"; }
159
my @rows = split_row($breakset,@children);
160
# Replace any "converted" leading operators (ie. invisible times => \times)
161
foreach my $row (@rows[1..$#rows]){
163
# print STDERR " Split at ".textContent($op)."\n";
165
if((nodeName($op) eq 'm:mo') && ($newop=$CONVERTOPS{textContent($op)})){
166
splice(@$op,2,scalar(@$op)-2, $newop); }}
167
if($LaTeXML::MathMLLineBreaker::PUNCT){
168
$rows[$#rows] = [@{$rows[$#rows]},$LaTeXML::MathMLLineBreaker::PUNCT];
169
$LaTeXML::MathMLLineBreaker::PUNCT = undef; }
171
my @firstrow = @{shift(@rows)};
172
splice(@$node,2,scalar(@children),
174
? ["m:mtable",{align=>'baseline 1', columnalign=>'left'},
176
["m:mtd",{}, @firstrow[0..$$layout{lhs_pos}-1]],
177
["m:mtd",{}, @firstrow[$$layout{lhs_pos}..$#firstrow]]],
180
["m:mtd",{},@$_]], @rows)]
181
: ["m:mtable",{align=>'baseline 1', columnalign=>'left'},
182
["m:mtr",{},["m:mtd",{}, @firstrow]],
184
["m:mtd",{}, ["m:mspace",{width=>$$layout{indentation}."em"}],@$_]],@rows)]));
187
# This would use <mspace> with linebreak attribute to break a row.
188
# Unfortunately, Mozillae ignore this attribute...
189
sub XXXXapplyLayout {
191
map(applyLayout($_), @{$$layout{children}} ) if $$layout{children};
192
if(my $breakset = $$layout{breakset}){
193
my $node = $$layout{node};
194
my @children = nodeChildren($node);
196
my @newchildren = ();
197
foreach my $line (split_row($breakset,@children)){
198
push(@newchildren,["m:mspace",{linebreak=>"indentingnewline"}]) if @newchildren;
199
push(@newchildren,@$line); }
200
splice(@$node,2,scalar(@children), @newchildren);
203
#######################################################################
204
# Utilities & Debugging aids
205
#######################################################################
209
my($tag,$attr,@children)=@$node;
214
my($tag,$attr,@children)=@$node;
219
my($tag,$attr,@children)=@$node;
224
my($tag,$attr,@children)=@$node;
225
join('',@children); }
227
sub min { (!defined $_[0] ? $_[1] : (!defined $_[1] ? $_[0] : ($_[0] < $_[1] ? $_[0] : $_[1]))); }
228
sub max { (!defined $_[0] ? $_[1] : (!defined $_[1] ? $_[0] : ($_[0] > $_[1] ? $_[0] : $_[1]))); }
230
sub describeLayouts {
231
my($self,$layouts)=@_;
232
my @layouts = @$layouts;
233
my $min = $layouts[0];
234
my $max = $layouts[-1];
235
print "Layout ".scalar(@layouts)." layout options\n"
236
." best = $$max{width} x ($$max{height} + $$max{depth}) penalty = $$max{penalty}\n"
237
." narrowest = $$min{width} x ($$min{height} + $$min{depth}) penalty = $$min{penalty}\n";
242
my($layout,$indent,$pos)=@_;
243
$indent = 0 unless $indent;
244
my $pre = (' ') x (2*$indent).(defined $pos ? "[$pos] ":"");
245
print $pre.layoutDescriptor($layout)."\n";
246
if($$layout{children}){
248
foreach my $child (@{$$layout{children}}){
249
showLayout($child,$indent+1,$p) if $$child{penalty};
253
sub layoutDescriptor {
256
."(".$$layout{width}." x ".$$layout{height}." + ".$$layout{depth}.")"
257
."@".$$layout{penalty}
259
? ", b@".join(",",map("[".join(',',@$_)."]",@{$$layout{breakset}}))
262
#######################################################################
264
#######################################################################
265
# multiplex(@layouts)
266
# Given a list of layouts arrays, (each representing the possible layouts of
267
# each of the children of a node), multiplex them to return a list of
268
# arrays containing one layout choice for each child.
269
# That is to say, form all combinations choosing one $layout
270
# from each of the $layouts lists in @layouts.
272
my($layouts,@siblings_layouts)=@_;
273
if(@siblings_layouts){
274
my @multiplexed_siblings_layouts = multiplex(@siblings_layouts);
275
my @multiplexed = ();
276
foreach my $layout (@$layouts){
277
foreach my $multiplexed_sibling_layout (@multiplexed_siblings_layouts){
278
push(@multiplexed, [$layout,@$multiplexed_sibling_layout]); }}
281
map([$_],@$layouts); }}
283
# Given a list of break's (in order), form all choices of breaks.
287
my $break = shift(@breaks);
288
map( ($_,[$break,@$_]),choices(@breaks)); }
291
#######################################################################
292
# Layout determination code
293
#######################################################################
294
# The current code computes pretty much every possible layout,
295
# which we'd select from later.
296
# If it turns out this is too expensive, it would be good
297
# to work out some kind of lazy expansion that could prune
298
# ridiculous layouts before computing everything, or even layouts
299
# that are worse than the current best for a given size.
300
# Of course, at this point, we don't know anything about the size
301
# of something till we do the full expansion, so...
302
#======================================================================
303
# "layouts" == [ layout, ...]
304
# represents a list of layout descriptors representing possible
305
# arrangements & breakpoints for a given node.
306
# Generally sorted by increasing width, and increasing penalty
307
# "layout" aka layout descriptor
308
# is a hash describing a possible layout arrangment.
310
# node == the node that this layout applies to.
311
# type == the tag name of the node, for convenience & debugging.
312
# width == width of this layout (in extremely rough ems)
313
# height == height of this layout box above baseline
314
# depth == depth below baseline
315
# penalty == undesirability of this layout
316
# children == [ layout, ...]
317
# being the layout descriptor for each child
318
# breakset == [ break,...]
319
# being a list of points to break at
320
# hasbreak = 1 if this node, or any children have a line-break.
321
# break = [pos,content,demerit] where pos is the (0 based) index of a breakpoint
322
# ie. make child[pos] start the next line.
323
#======================================================================
325
my($node,$target,$level,$displaystyle,$scriptlevel,$demerits)=@_;
327
# Get the Handler for this tag
328
my $name = nodeName($node);
330
my $handler = "layout_$name";
331
eval { $handler = \&$handler; };
332
print STDERR "",(' ' x $level),"layout $name: ",$node,"...\n" if $DEBUG > 1;
334
# Get the handler to compute the layouts
335
my $layouts = &$handler($node,$target,$level, $displaystyle||0, $scriptlevel||0, $demerits||1);
336
my $nlayouts = scalar(@$layouts);
338
# Sort & prune the layouts
339
my @layouts = prunesort($target,@$layouts);
340
my $pruned = scalar(@layouts);
342
print STDERR "",(' ' x $level),"$name: $nlayouts layouts"
343
.($pruned < $nlayouts ? " pruned to $pruned":"")
344
." ".layoutDescriptor($$layouts[0])
345
.($nlayouts > 1 ? "...".layoutDescriptor($$layouts[$nlayouts-1]) : "")
350
my($target,@layouts)=@_;
351
@layouts = sort { ($$a{width} <=> $$b{width}) || ($$a{penalty} <=> $$b{penalty}) } @layouts;
352
my @goodlayouts= ( shift(@layouts) ); # always include at least the shortest/best
353
foreach my $layout (@layouts){
354
if(($$layout{width} < $target) # If not too wide
355
&& ($goodlayouts[$#goodlayouts]->{penalty} > $$layout{penalty})){ # not worse than prev
356
push(@goodlayouts,$layout); }}
359
#######################################################################
361
# This is the real workhorse.
362
#######################################################################
363
# Here, of course, is where the Interesting stuff will happen.
366
my($node,$target,$level,$displaystyle,$scriptlevel,$demerits)=@_;
367
my $type = nodeName($node);
368
my @children = nodeChildren($node);
369
if(grep( ref $_ ne 'ARRAY', @children)){
370
die "ROW has non-element: ".nodeName($node); }
371
my $n = scalar(@children);
373
return [ { node=>$node, type=>$type,
374
penalty=>0, width => 0, height => 0, depth=> 0} ]; }
376
# Multiple children, possibly with breaks
377
# Get the set of layouts for each child
378
## my @child_layouts = map(layout($_,$target,$level+1,$displaystyle,$scriptlevel,$demerits),
379
my @child_layouts = map(layout($_,$target,$level+1,$displaystyle,$scriptlevel,$demerits+1),
382
# Now, we need all possible break points within the row itself.
384
my ($lhs_pos,$lhs_width);
385
if($demerits < $NOBREAK){
386
for(my $i=1; $i<$n-1; $i++){
387
my $child = $children[$i];
388
my $content = (nodeName($child) eq 'm:mo') && textContent($child);
390
elsif($RELATIONOPS{$content}){
391
push(@breaks, [$i,$content,$demerits]);
392
if(! defined $lhs_pos){
394
$lhs_width = sum(map($$_[-1]{width}, @child_layouts[0..$i-1]));
395
$lhs_pos = 0 if $lhs_width > $target/4; }}
396
elsif($BREAKOPS{$content}){
398
push(@breaks, [$i,$content,$demerits]); }
399
elsif($CONVERTOPS{$content}){
401
## push(@breaks, [$i,$content,$demerits*$CONVERSION_FACTOR]); }
402
push(@breaks, [$i,$content,$demerits+$CONVERSION_FACTOR]); }
404
my $indentation = ($lhs_pos ? $lhs_width : 2);
406
# Form the set of all choices from the breaks.
407
my @breaksets = choices(@breaks);
409
print STDERR "",(" " x $level), $type," ",
410
join("x", map(scalar(@$_),@child_layouts))," layouts",
412
? ", breaks@".join(",",map("[".join(',',@$_)."]",@breaks))
413
."(".scalar(@breaksets)." sets;"
414
. product(map(scalar(@$_),@child_layouts),scalar(@breaksets))." combinations"
420
# For each set of breaks within this row
421
# And for each layout of children
422
# Form composite layouts, computing the size, penalty, and pruning.
423
# NOTE: Would we like to prefer more evenly balanced splits?
425
BREAKSET: foreach my $breakset (reverse @breaksets){ # prunes better reversed?
426
# Since we only allow to break the last child in a row,
427
# form subset of children's layouts
428
# Namely, take only last (unbroken?) layout for all but last child in each row
429
my @filtered_child_layouts=();
430
foreach my $xline (split_row($breakset,@child_layouts)){
431
my @xline_children_layouts = @$xline;
432
while(@xline_children_layouts){
433
my $xchild_layouts = shift(@xline_children_layouts);
434
if(@xline_children_layouts){ # More children?
435
my @x = @$xchild_layouts;
437
next BREAKSET if $$last{hasbreak};
438
push(@filtered_child_layouts,[$last]); } # take last
440
push(@filtered_child_layouts,$xchild_layouts); }}}
441
my @children_layouts = multiplex(@filtered_child_layouts);
443
LAYOUT: foreach my $children_layout (@children_layouts){
444
my($width,$height,$depth,$penalty,$indent,$rowheight)=(0,0,0,0,0,0);
445
$penalty = sum(map($$_[2],@$breakset));
446
# Last (best) layout, for comparison & pruning
447
my $last = (@layouts && $layouts[$#layouts]);
448
# Apply the breaks to split the children (actually their layout) into lines.
449
foreach my $line (split_row($breakset,@$children_layout)){
450
my($w,$h,$d)=(0,0,0);
451
my @line_children_layout = @$line;
452
while(@line_children_layout){ # For each line of nodes, compute sizes, possibly prune
453
my $child_layout = shift(@line_children_layout);
454
$w += $$child_layout{width};
455
$penalty += $$child_layout{penalty}||0;
456
# Skip to next breakset if we've gotten too wide, or worse than previous
457
if($last && (($w > 1.5*$target)
458
# || (($$last{width} < 1.5*$target) && ($penalty > $$last{penalty})))){
459
|| (($$last{width} <= $w) && ($penalty > $$last{penalty})))){
463
$h = max($h,$$child_layout{height});
464
$d = max($d,$$child_layout{depth});
466
# Then combine the lines
467
$width = max($width,$w+$indent);
468
$indent = $indentation;
474
$rowheight = max($rowheight,$h+$d); }
475
push(@layouts, { node=>$node,type=>$type,
477
width=>$width, height=>$height, depth=>$depth,
478
area=>$width*($depth+$height),
479
indentation=>$indentation, rowheight=>$rowheight, lhs_pos=>$lhs_pos,
480
(scalar(@$breakset) ? (breakset=>$breakset):()),
481
hasbreak=>scalar(@$breakset)||scalar(grep($$_{hasbreak},@$children_layout)),
482
children=>[@$children_layout]});
483
@layouts = prunesort($target,@layouts); }}
485
## @layouts = prunesort($target,@layouts);
487
## Add a penalty for smallest area (?)
488
my($maxarea,$minarea) = (0,999999999);
489
map($maxarea = max($maxarea,$$_{area}),@layouts);
490
map($minarea = min($minarea,$$_{area}),@layouts);
491
map( $$_{penalty} *= (1+($$_{area} -$minarea)/$maxarea), @layouts) if $maxarea > $minarea;
492
@layouts = prunesort($target,@layouts);
494
## print STDERR "",(" " x $level), $type," pruned $pruned\n" if $pruned && ($DEBUG>1);
495
Warn("Row (".nodeName($node).") got no layouts!") unless @layouts;
499
my($breakset,@stuff)=@_;
502
foreach my $break (@$breakset){
503
my($breakpos,$content,$demerit)=@$break;
504
push(@lines, [ @stuff[$pos..$breakpos-1] ]);
506
push(@lines, [ @stuff[$pos..$#stuff] ]);
509
#######################################################################
510
# Layout handlers for various MathML tags
511
# These are called layout_<tag>
512
#######################################################################
514
#======================================================================
516
#======================================================================
517
# These are just empty.
519
my($node,$target,$level,$displaystyle,$scriptlevel,$demerits)=@_;
520
my $content = textContent($node);
521
$scriptlevel = min(0,max($scriptlevel,3));
522
[ { node=>$node, type=>"none",
523
penalty=>0, width => 0, height => 0, depth=> 0} ]; }
526
my($node,$target,$level,$displaystyle,$scriptlevel,$demerits)=@_;
527
my $content = textContent($node);
528
$scriptlevel = min(0,max($scriptlevel,3));
529
[ { node=>$node, type=>"empty",
530
penalty=>0, width => 0, height => 0, depth=> 0} ]; }
532
#======================================================================
534
#======================================================================
535
# These are just simple boxes that don't break.
536
# Approximate their size.
538
our @SIZE=(1.0, 0.71, 0.71*0.71,0.71*0.71*0.71);
540
# TODO for mo: largeop ?
542
my($node,$target,$level,$displaystyle,$scriptlevel,$demerits)=@_;
543
my $content = textContent($node);
544
$scriptlevel = min(0,max($scriptlevel,3));
545
[ { node=>$node, type=>nodeName($node), penalty=>0,
546
width => length($content)*$SIZE[$scriptlevel],
547
height => $SIZE[$scriptlevel],
550
sub layout_mi { simpleSize(@_); }
551
sub layout_mo { simpleSize(@_); }
552
sub layout_mn { simpleSize(@_); }
553
sub layout_mtext { simpleSize(@_); }
554
sub layout_merror { simpleSize(@_); }
556
#======================================================================
557
# Various row-line things.
558
#======================================================================
559
sub layout_mrow { asRow(@_); }
560
sub layout_mpadded { asRow(@_); }
561
sub layout_mphantom { asRow(@_); }
562
sub layout_menclose { asRow(@_); }
563
sub layout_mfenced { asRow(@_); } # Close enough?
566
my($node,$target,$level,$displaystyle,$scriptlevel,$demerits)=@_;
567
my $selection = getAttribute($node,'selection') || 0;
568
my @children = nodeChildren($node);
569
layout($children[$selection],$target,$level,$displaystyle,$scriptlevel,$demerits); }
572
my($node,$target,$level,$displaystyle,$scriptlevel,$demerits)=@_;
573
if(my $d = getAttribute($node,'displaystyle')){
574
$displaystyle = ($d eq 'true'); }
575
if(my $s = getAttribute($node,'scriptlevel')){
576
if ($s =~ /^\+(\d+)$/){ $scriptlevel += $1; }
577
elsif($s =~ /^\-(\d+)$/){ $scriptlevel -= $1; }
578
elsif($s =~ /^(\d+)$/ ){ $scriptlevel = $1; }}
579
asRow($node,$target,$level,$displaystyle, $scriptlevel,$demerits); }
581
#======================================================================
583
#======================================================================
584
# These allow the children to break, but with heavier penalty.
587
my($node,$target,$level,$displaystyle,$scriptlevel,$demerits)=@_;
588
# No break of mfrac itself
589
# 2 children; break of children is poor
590
[map( { node=>$node, type=>'mfrac',
591
penalty => $$_[0]->{penalty} + $$_[1]->{penalty},
592
width => max($$_[0]->{width},$$_[1]->{width}),
593
height => $$_[0]->{height} + $$_[0]->{depth} + 1,
594
depth => $$_[1]->{height} + $$_[1]->{depth},
595
hasbreak => $$_[0]->{hasbreak} || $$_[1]->{hasbreak},
597
multiplex(map( layout($_, $target,$level+1,0 ,$scriptlevel, $demerits*$POORBREAK_FACTOR),
598
nodeChildren($node))))]; }
601
my($node,$target,$level,$displaystyle,$scriptlevel,$demerits)=@_;
602
# no break of mroot itself, index doesn't break, break of base is bad
603
my ($base,$index) = nodeChildren($node);
604
my $indexlayout = layout($index,$target,$level+1, 0 ,$scriptlevel+1, $NOBREAK)->[0];
605
$target -= $$indexlayout{width};
606
my $baselayouts = layout($base, $target,$level+1, 0 ,$scriptlevel, $demerits*$BADBREAK_FACTOR);
607
[map( { node=>$node, type=>'mroot',
608
penalty => $$_[0]->{penalty} + $$_[1]->{penalty},
609
width => $$_[0]->{width} + $$_[1]->{width},
610
height => $$_[0]->{height},
611
depth => $$_[0]->{depth},
612
hasbreak => $$_[0]->{hasbreak} || $$_[1]->{hasbreak},
614
multiplex($baselayouts,[$indexlayout]))]; }
617
my($node,$target,$level,$displaystyle,$scriptlevel,$demerits)=@_;
618
# no break of msqrt itself,
619
# 1 child or implied mrow; bad to break
620
asRow($node,$target,$level+1,$displaystyle,$scriptlevel,$demerits*$BADBREAK_FACTOR); }
622
#======================================================================
624
#======================================================================
625
# We're not allowing breaks within tables, but we still have a mess of sums & max's
628
my($node,$target,$level,$displaystyle,$scriptlevel,$demerits)=@_;
632
foreach my $row (nodeChildren($node)){
635
foreach my $col (nodeChildren($row)){
636
my $layout = layout($col,$target,$level+1, 0 ,$scriptlevel+1, $NOBREAK)->[0];
637
$widths[$i] = max($widths[$i] || 0, $$layout{width});
638
$h = max($h,$$layout{height});
639
$d = max($d,$$layout{depth});
641
push(@heights,$h); push(@depths,$d); }
642
my $width = sum(@widths);
644
my $align = getAttribute($node,'align') || 'axis';
645
my $n = scalar(@heights);
646
if($align =~ s/(\d+)//){
648
($height,$depth) = tableVAlignment($align,$heights[$i-1],$depths[$i-1]);
649
$height += sum(@heights[0..$i-2]) + sum(@depths[0..$i-2]) if $i > 1;
650
$depth += sum(@heights[$i..$n-1]) + sum(@depths[$i..$n-1]) if $i < $n; }
652
$height = (sum(@heights)+sum(@depths))/2; $depth = $height;
653
($height,$depth) = tableVAlignment($align,$height,$depth); }
654
[ { node=>$node, type=>nodeName($node),
655
penalty => 0, width => $width, height => $height, depth => $depth} ]; }
657
sub tableVAlignment {
658
my($align,$height,$depth)=@_;
659
if ($align eq 'top') { $depth = $height+$depth; $height = 0; }
660
elsif($align eq 'bottom') { $height= $height+$depth; $depth = 0; }
661
elsif($align eq 'center') { $height=($height+$depth)/2; $depth = $height; }
662
elsif($align eq 'axis') { $height=($height+$depth)/2; $depth = $height; }
663
elsif($align eq 'baseline'){}
681
#sub layout_mlabeledtr {}
682
sub layout_mtd { asRow(@_); }
684
#======================================================================
685
# Various sub & super scripts
686
#======================================================================
687
# TODO: What about movablelimits, accent on base ?
689
my($node,$target,$level,$displaystyle,$scriptlevel,$demerits,
690
$stacked,$basenode,@scriptnodes)=@_;
691
# Scripts do not break, base is poor to break.
693
foreach my $layoutset (multiplex(layout($basenode,$target,$level+1,
694
$displaystyle,$scriptlevel,$demerits*$POORBREAK_FACTOR),
695
map(layout($_,$target,$level+1,0,$scriptlevel+1,$NOBREAK),
697
my($base,@scripts)=@$layoutset;
698
my($width,$height,$depth,$penalty)=(0,0,0,0);
700
my $sub = shift(@scripts);
701
my $sup = shift(@scripts);
702
$width += max($$sub{width},$$sup{width});
703
$height += max($height,$$sup{depth}+$$sup{height}); # Roughly..
704
$depth += max($depth, $$sub{depth}+$$sub{height});
705
$penalty+= $$sub{penalty}+$$sup{penalty}; }
706
$penalty += $$base{penalty};
708
$width = max($width,$$base{width});
709
$height += $$base{height};
710
$depth += $$base{depth}; }
712
$width += $$base{width};
713
$height = $$base{height} + 0.5*$height;
714
$depth = $$base{depth} + 0.5*$depth; }
715
push(@layouts,{ node=>$node, type=>nodeName($node),
716
penalty => $penalty, width => $width, height => $height, depth => $depth,
717
hasbreak => scalar(grep($$_{hasbreak}, @$layoutset)),
718
children => $layoutset}); }
722
my($node,$target,$level,$displaystyle,$scriptlevel,$demerits)=@_;
723
my($base,$sub)=nodeChildren($node);
724
asScripts($node,$target,$level,$displaystyle,$scriptlevel,$demerits, 0,$base,$sub,['m:none']); }
727
my($node,$target,$level,$displaystyle,$scriptlevel,$demerits)=@_;
728
my($base,$sup)=nodeChildren($node);
729
asScripts($node,$target,$level,$displaystyle,$scriptlevel,$demerits, 0,$base,['m:none'],$sup); }
732
my($node,$target,$level,$displaystyle,$scriptlevel,$demerits)=@_;
733
my($base,$sub,$sup)=nodeChildren($node);
734
asScripts($node,$target,$level,$displaystyle,$scriptlevel,$demerits, 0,$base,$sub,$sup); }
737
my($node,$target,$level,$displaystyle,$scriptlevel,$demerits)=@_;
738
my($base,$sub)=nodeChildren($node);
739
asScripts($node,$target,$level,$displaystyle,$scriptlevel,$demerits, 1,$base,$sub,['m:none']); }
742
my($node,$target,$level,$displaystyle,$scriptlevel,$demerits)=@_;
743
my($base,$sup)=nodeChildren($node);
744
asScripts($node,$target,$level,$displaystyle,$scriptlevel,$demerits, 1,$base,['m:none'],$sup); }
746
sub layout_munderover {
747
my($node,$target,$level,$displaystyle,$scriptlevel,$demerits)=@_;
748
my($base,$sub,$sup)=nodeChildren($node);
749
asScripts($node,$target,$level,$displaystyle,$scriptlevel,$demerits, 1,$base,$sub,$sup); }
751
sub layout_mmultiscripts {
752
my($node,$target,$level,$displaystyle,$scriptlevel,$demerits)=@_;
753
my($base,@scripts)=nodeChildren($node);
754
@scripts = grep( nodeName($_) ne "m:mprescripts", @scripts); # Remove prescripts marker (if any).
755
asScripts($node,$target,$level,$displaystyle,$scriptlevel,$demerits, 0,$base,@scripts); }
757
#======================================================================