~ubuntu-branches/ubuntu/oneiric/latexml/oneiric

« back to all changes in this revision

Viewing changes to lib/LaTeXML/Util/MathMLLinebreaker.pm

  • Committer: Bazaar Package Importer
  • Author(s): Atsuhito KOHDA
  • Date: 2010-06-09 08:15:06 UTC
  • Revision ID: james.westby@ubuntu.com-20100609081506-1asj0n4u3w4q6jem
Tags: upstream-0.7.0
ImportĀ upstreamĀ versionĀ 0.7.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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=/ #
 
12
 
 
13
package LaTeXML::Util::MathMLLinebreaker;
 
14
#======================================================================
 
15
# General MathML Line-Breaking Strategy.
 
16
#
 
17
# (1) Preparations: if top-level has trailing punctuation,
 
18
#    remove it to be added back, later.
 
19
#
 
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]
 
26
#
 
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.
 
31
#
 
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.
 
35
#
 
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.
 
43
 
 
44
use strict;
 
45
 
 
46
#######################################################################
 
47
# Parameters
 
48
#######################################################################
 
49
our $DEBUG = 0;
 
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
 
54
 
 
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]); }
 
59
 
 
60
our  %BREAKOPS = map(($_=>1),
 
61
                     # Various addops
 
62
                     "+","-",UTF(0xB1),"\x{2213}", # pm, mp
 
63
                     );
 
64
our  %RELATIONOPS = map(($_=>1),
 
65
                     # Various relops
 
66
                     "=", "<",">", "\x{2264}","\x{2265}","\x{2260}","\x{226A}",
 
67
                     "\x{2261}","\x{223C}","\x{2243}","\x{224D}","\x{2248}","\x{2260}","\x{221D}",
 
68
                     );
 
69
our  %CONVERTOPS = ("\x{2062}"=>UTF(0xD7), # Invisible (discretionary) times
 
70
                   );
 
71
binmode(STDOUT,":utf8") if $DEBUG;
 
72
 
 
73
#######################################################################
 
74
# Top-level interface
 
75
#######################################################################
 
76
 
 
77
sub new {
 
78
  my($class)=@_; 
 
79
  my $self = bless {},$class; 
 
80
  $self; }
 
81
 
 
82
sub fitToWidth {
 
83
  my($self,$math,$mml,$width,$displaystyle)=@_;
 
84
  # Check for end punctuation; Remove it, if found.
 
85
  my @n;
 
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]) =~ /^[\.\,\;]$/)){
 
90
    $mml = $n[0];
 
91
    $LaTeXML::MathMLLineBreaker::PUNCT = $n[1]; }
 
92
 
 
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);
 
96
  if($DEBUG){
 
97
    print STDERR "Got ".scalar(@$layouts)." layouts:\n";
 
98
    map(showLayout($_),@$layouts); }
 
99
 
 
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?
 
103
  applyLayout($best);
 
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]; }
 
107
 
 
108
  Warn("Got width $$best{width} > $width") if $$best{width} > $width+1;
 
109
  $mml; }
 
110
 
 
111
sub Warn {
 
112
  my($message)=@_;
 
113
  my $p = $LaTeXML::MathMLLineBreaker::MATH;
 
114
  while($p && !$p->hasAttribute('xml:id')){
 
115
    $p=$p->parentNode; }
 
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"; }
 
119
 
 
120
 
 
121
#######################################################################
 
122
# Apply the breaks described in a layout to the MathML.
 
123
#######################################################################
 
124
# Modify the mathml to incorporate a set of breaks.
 
125
 
 
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.
 
134
sub applyLayout {
 
135
  my($layout)=@_;
 
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"; }
 
158
    }
 
159
    my @rows = split_row($breakset,@children);
 
160
    # Replace any "converted" leading operators (ie. invisible times => \times)
 
161
    foreach my $row (@rows[1..$#rows]){
 
162
      my $op = $$row[0];
 
163
# print STDERR "  Split at ".textContent($op)."\n";
 
164
      my $newop;
 
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; }
 
170
 
 
171
    my @firstrow = @{shift(@rows)};
 
172
    splice(@$node,2,scalar(@children),
 
173
           ($$layout{lhs_pos}
 
174
            ? ["m:mtable",{align=>'baseline 1', columnalign=>'left'},
 
175
               ["m:mtr",{},
 
176
                ["m:mtd",{}, @firstrow[0..$$layout{lhs_pos}-1]],
 
177
                ["m:mtd",{}, @firstrow[$$layout{lhs_pos}..$#firstrow]]],
 
178
               map( ["m:mtr",{},
 
179
                     ["m:mtd",{}],
 
180
                     ["m:mtd",{},@$_]], @rows)]
 
181
            : ["m:mtable",{align=>'baseline 1', columnalign=>'left'},
 
182
               ["m:mtr",{},["m:mtd",{}, @firstrow]],
 
183
               map( ["m:mtr",{},
 
184
                     ["m:mtd",{}, ["m:mspace",{width=>$$layout{indentation}."em"}],@$_]],@rows)]));
 
185
  }}
 
186
 
 
187
# This would use <mspace> with linebreak attribute to break a row.
 
188
# Unfortunately, Mozillae ignore this attribute...
 
189
sub XXXXapplyLayout {
 
190
  my($layout)=@_;
 
191
  map(applyLayout($_), @{$$layout{children}} ) if $$layout{children};
 
192
  if(my $breakset = $$layout{breakset}){
 
193
    my $node = $$layout{node};
 
194
    my @children = nodeChildren($node);
 
195
    my @lines = 
 
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);
 
201
  }}
 
202
 
 
203
#######################################################################
 
204
# Utilities & Debugging aids
 
205
#######################################################################
 
206
 
 
207
sub nodeName {
 
208
  my($node)=@_;
 
209
  my($tag,$attr,@children)=@$node;
 
210
  $tag; }
 
211
 
 
212
sub getAttribute {
 
213
  my($node,$key)=@_;
 
214
  my($tag,$attr,@children)=@$node;
 
215
  $$attr{$key}; }
 
216
 
 
217
sub nodeChildren {
 
218
  my($node)=@_;
 
219
  my($tag,$attr,@children)=@$node;
 
220
  @children; }
 
221
 
 
222
sub textContent {
 
223
  my($node)=@_;
 
224
  my($tag,$attr,@children)=@$node;
 
225
  join('',@children); }
 
226
 
 
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]))); }
 
229
 
 
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"; 
 
238
  showLayout($max);
 
239
}
 
240
 
 
241
sub showLayout {
 
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}){
 
247
    my $p = 0;
 
248
    foreach my $child (@{$$layout{children}}){
 
249
      showLayout($child,$indent+1,$p) if $$child{penalty}; 
 
250
      $p++; }}
 
251
}
 
252
 
 
253
sub layoutDescriptor {
 
254
  my($layout)=@_;
 
255
  $$layout{type}." "
 
256
    ."(".$$layout{width}." x ".$$layout{height}." + ".$$layout{depth}.")"
 
257
      ."@".$$layout{penalty}
 
258
        .($$layout{breakset}
 
259
          ? ", b@".join(",",map("[".join(',',@$_)."]",@{$$layout{breakset}}))
 
260
          : ""); }
 
261
 
 
262
#######################################################################
 
263
# Permutation things
 
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.
 
271
sub multiplex {
 
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]); }}
 
279
    @multiplexed; }
 
280
  else {
 
281
    map([$_],@$layouts); }}
 
282
 
 
283
# Given a list of break's (in order), form all choices of breaks.
 
284
sub choices {
 
285
  my(@breaks)=@_;
 
286
  if(@breaks){
 
287
    my $break = shift(@breaks);
 
288
    map( ($_,[$break,@$_]),choices(@breaks)); }
 
289
  else { ([]); }}
 
290
 
 
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.
 
309
#     The fields are:
 
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
#======================================================================
 
324
sub layout {
 
325
  my($node,$target,$level,$displaystyle,$scriptlevel,$demerits)=@_;
 
326
 
 
327
  # Get the Handler for this tag
 
328
  my $name = nodeName($node);
 
329
  $name =~ s/\w://;
 
330
  my $handler = "layout_$name";
 
331
  eval { $handler = \&$handler; };
 
332
  print STDERR "",('  ' x $level),"layout $name: ",$node,"...\n" if $DEBUG > 1;
 
333
 
 
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);
 
337
 
 
338
  # Sort & prune the layouts
 
339
  my @layouts = prunesort($target,@$layouts);
 
340
  my $pruned = scalar(@layouts);
 
341
 
 
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]) : "")
 
346
      ."\n" if $DEBUG > 1;
 
347
  [@layouts]; }
 
348
 
 
349
sub prunesort {
 
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); }}
 
357
  @goodlayouts; }
 
358
 
 
359
#######################################################################
 
360
# Row line breaker.
 
361
# This is the real workhorse.
 
362
#######################################################################
 
363
# Here, of course, is where the Interesting stuff will happen.
 
364
 
 
365
sub asRow {
 
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);
 
372
  if(!$n){
 
373
    return [ { node=>$node, type=>$type,
 
374
               penalty=>0, width   => 0, height  => 0, depth=> 0} ]; }
 
375
 
 
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),
 
380
                          @children);
 
381
 
 
382
  # Now, we need all possible break points within the row itself.
 
383
  my @breaks = ();
 
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);
 
389
      if(!$content){}
 
390
      elsif($RELATIONOPS{$content}){
 
391
        push(@breaks, [$i,$content,$demerits]); 
 
392
        if(! defined $lhs_pos){
 
393
          $lhs_pos = $i;
 
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}){
 
397
        $lhs_pos = 0;
 
398
        push(@breaks, [$i,$content,$demerits]); }
 
399
      elsif($CONVERTOPS{$content}){
 
400
        $lhs_pos = 0;
 
401
##      push(@breaks, [$i,$content,$demerits*$CONVERSION_FACTOR]); }
 
402
        push(@breaks, [$i,$content,$demerits+$CONVERSION_FACTOR]); }
 
403
    }}
 
404
  my $indentation = ($lhs_pos ? $lhs_width : 2);
 
405
 
 
406
  # Form the set of all choices from the breaks.
 
407
  my @breaksets = choices(@breaks);
 
408
 
 
409
  print STDERR "",("  " x $level), $type," ",
 
410
      join("x", map(scalar(@$_),@child_layouts))," layouts",
 
411
        (@breaks
 
412
         ? ", breaks@".join(",",map("[".join(',',@$_)."]",@breaks))
 
413
         ."(".scalar(@breaksets)." sets;"
 
414
         .    product(map(scalar(@$_),@child_layouts),scalar(@breaksets))." combinations"
 
415
         .")"
 
416
         : "")."\n"
 
417
    if $DEBUG > 1;
 
418
 
 
419
  my @layouts = ();
 
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?
 
424
  my $pruned=0;
 
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;
 
436
          my $last = $x[$#x];
 
437
          next BREAKSET if $$last{hasbreak};
 
438
          push(@filtered_child_layouts,[$last]); } # take last
 
439
        else {
 
440
          push(@filtered_child_layouts,$xchild_layouts); }}}
 
441
    my @children_layouts = multiplex(@filtered_child_layouts);
 
442
 
 
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})))){
 
460
              $pruned++;
 
461
              next LAYOUT; }
 
462
 
 
463
            $h = max($h,$$child_layout{height});
 
464
            $d = max($d,$$child_layout{depth});
 
465
          }
 
466
          # Then combine the lines
 
467
          $width = max($width,$w+$indent);
 
468
          $indent = $indentation;
 
469
          if($height == 0){
 
470
            $height = $h;
 
471
            $depth  = $d; }
 
472
          else {
 
473
            $depth += $h + $d; }
 
474
          $rowheight = max($rowheight,$h+$d); }
 
475
        push(@layouts, { node=>$node,type=>$type,
 
476
                         penalty=>$penalty,
 
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); }}
 
484
##      }}
 
485
##  @layouts = prunesort($target,@layouts);
 
486
 
 
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);
 
493
 
 
494
##  print STDERR "",("  " x $level), $type," pruned $pruned\n" if $pruned && ($DEBUG>1);
 
495
  Warn("Row (".nodeName($node).") got no layouts!") unless @layouts;
 
496
  [@layouts]; }
 
497
 
 
498
sub split_row {
 
499
  my($breakset,@stuff)=@_;
 
500
  my @lines=();
 
501
  my $pos=0;
 
502
  foreach my $break (@$breakset){
 
503
    my($breakpos,$content,$demerit)=@$break;
 
504
    push(@lines, [ @stuff[$pos..$breakpos-1] ]);
 
505
    $pos = $breakpos; }
 
506
  push(@lines, [ @stuff[$pos..$#stuff] ]);
 
507
  @lines; }
 
508
 
 
509
#######################################################################
 
510
# Layout handlers for various MathML tags
 
511
# These are called layout_<tag>
 
512
#######################################################################
 
513
 
 
514
#======================================================================
 
515
# Trivial cases.
 
516
#======================================================================
 
517
# These are just empty.
 
518
sub layout_none {
 
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} ]; }
 
524
 
 
525
sub layout_empty {
 
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} ]; }
 
531
 
 
532
#======================================================================
 
533
# Simple cases.
 
534
#======================================================================
 
535
# These are just simple boxes that don't break.
 
536
# Approximate their size.
 
537
 
 
538
our @SIZE=(1.0, 0.71, 0.71*0.71,0.71*0.71*0.71);
 
539
# TODO: spacing ?
 
540
# TODO for mo:  largeop ?
 
541
sub simpleSize {
 
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],
 
548
      depth=> 0} ]; }
 
549
 
 
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(@_); }
 
555
 
 
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?
 
564
 
 
565
sub layout_maction { 
 
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); }
 
570
 
 
571
sub layout_mstyle  { 
 
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); }
 
580
 
 
581
#======================================================================
 
582
# Fractions & Roots
 
583
#======================================================================
 
584
# These allow the children to break, but with heavier penalty.
 
585
 
 
586
sub layout_mfrac {
 
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},
 
596
          children=>$_},
 
597
        multiplex(map( layout($_, $target,$level+1,0 ,$scriptlevel, $demerits*$POORBREAK_FACTOR),
 
598
                       nodeChildren($node))))]; }
 
599
 
 
600
sub layout_mroot {
 
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},
 
613
          children => $_},
 
614
        multiplex($baselayouts,[$indexlayout]))]; }
 
615
 
 
616
sub layout_msqrt {
 
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); }
 
621
 
 
622
#======================================================================
 
623
# Tables
 
624
#======================================================================
 
625
# We're not allowing breaks within tables, but we still have a mess of sums & max's
 
626
 
 
627
sub layout_mtable {
 
628
  my($node,$target,$level,$displaystyle,$scriptlevel,$demerits)=@_;
 
629
  my @widths=();
 
630
  my @heights=();
 
631
  my @depths=();
 
632
  foreach my $row (nodeChildren($node)){
 
633
    my ($h,$d)=(0,0);
 
634
    my $i = 0;
 
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}); 
 
640
      $i++; }
 
641
    push(@heights,$h); push(@depths,$d); }
 
642
  my $width = sum(@widths);
 
643
  my($height,$depth);
 
644
  my $align = getAttribute($node,'align') || 'axis';
 
645
  my $n = scalar(@heights);
 
646
  if($align =~ s/(\d+)//){
 
647
    my $i = $1;
 
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;  }
 
651
  else {
 
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} ]; }
 
656
 
 
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'){}
 
664
  ($height,$depth); }
 
665
 
 
666
sub sum { 
 
667
  my(@x)=@_;
 
668
  my $sum = 0;
 
669
  foreach my $x (@x) {
 
670
    $sum += $x || 0; }
 
671
  $sum; }
 
672
 
 
673
sub product { 
 
674
  my(@x)=@_;
 
675
  my $prod = 1;
 
676
  foreach my $x (@x) {
 
677
    $prod *= $x || 1; }
 
678
  $prod; }
 
679
 
 
680
#sub layout_mtr {}
 
681
#sub layout_mlabeledtr {}
 
682
sub layout_mtd { asRow(@_); }
 
683
 
 
684
#======================================================================
 
685
# Various sub & super scripts
 
686
#======================================================================
 
687
# TODO: What about movablelimits, accent on base ?
 
688
sub asScripts {
 
689
  my($node,$target,$level,$displaystyle,$scriptlevel,$demerits,
 
690
     $stacked,$basenode,@scriptnodes)=@_;
 
691
  # Scripts do not break, base is poor to break.
 
692
  my @layouts = ();
 
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),
 
696
                                       @scriptnodes))){
 
697
    my($base,@scripts)=@$layoutset;
 
698
    my($width,$height,$depth,$penalty)=(0,0,0,0);
 
699
    while(@scripts){
 
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};
 
707
    if($stacked){
 
708
      $width   = max($width,$$base{width});
 
709
      $height += $$base{height};
 
710
      $depth  += $$base{depth}; }
 
711
    else {
 
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}); }
 
719
  [@layouts]; }
 
720
 
 
721
sub layout_msub {
 
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']); }
 
725
 
 
726
sub layout_msup {
 
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); }
 
730
 
 
731
sub layout_msubsup {
 
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); }
 
735
 
 
736
sub layout_munder {
 
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']); }
 
740
 
 
741
sub layout_mover {
 
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); }
 
745
 
 
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); }
 
750
 
 
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); }
 
756
 
 
757
#======================================================================
 
758
1;