1"""Functions to determine size and position of MathML elements"""
2import sys, math, mathnode, operators, tables, enclosures
3
4# Handy constant to draw fraction bars
5defaultSlope = 1.383
6
7def default_measure (node): pass
8
9def measure_none (node): pass
10
11def measure_mprescripts (node): pass
12
13def measure_math (node): measure_mrow (node)
14
15def measure_mphantom (node): measure_mrow (node)
16
17def measure_mstyle (node):
18    measure_mrow(node)  # processed in the constructor
19
20def measure_maction (node):
21    selectionattr = node.attributes.get(u"selection", u"1")
22    selection = node.parseInt(selectionattr)
23    node.base = None
24    if selection <= 0:
25        node.error("Invalid value '%s' for 'selection' attribute - not a positive integer" % selectionattr)
26    elif len(node.children) == 0:
27        node.error("No valid subexpression inside maction element - element ignored" % selectionattr)
28    else:
29        if selection > len(node.children):
30            node.error("Invalid value '%d' for 'selection' attribute - there are only %d expression descendants in the element" % (selection, len(node.children)))
31            selection = 1
32        setNodeBase(node, node.children[selection - 1])
33        node.width = node.base.width
34        node.height = node.base.height
35        node.depth = node.base.depth
36        node.ascender = node.base.ascender
37        node.descender = node.base.descender
38
39def measure_mpadded (node):
40    createImplicitRow(node)
41
42    def parseDimension(attr, startvalue, canUseSpaces):
43        if attr.endswith(" height"):
44            basevalue = node.base.height
45            attr = attr [:-7]
46        elif attr.endswith(" depth"):
47            basevalue = node.base.depth
48            attr = attr [:-6]
49        elif attr.endswith(" width"):
50            basevalue = node.base.width
51            attr = attr [:-6]
52        else: basevalue = startvalue
53
54        if attr.endswith (u'%'):
55            attr = attr[:-1]
56            basevalue /= 100.0
57
58        if canUseSpaces:
59            return node.parseSpace(attr, basevalue)
60        else:
61            return node.parseLength(attr, basevalue)
62
63    def getDimension(attname, startvalue, canUseSpaces):
64        attr = node.attributes.get(attname)
65        if attr is None: return startvalue
66        attr = " ".join(attr.split())
67        if attr.startswith(u'+'):
68            return startvalue + parseDimension(attr[1:], startvalue, canUseSpaces)
69        elif attr.startswith(u'+'):
70            return startvalue - parseDimension(attr[1:], startvalue, canUseSpaces)
71        else:
72            return parseDimension(attr, startvalue, canUseSpaces)
73
74    node.height = getDimension(u"height", node.base.height, False)
75    node.depth = getDimension(u"depth", node.base.depth, False)
76    node.ascender = node.base.ascender
77    node.descender = node.base.descender
78    node.leftpadding = getDimension(u"lspace", 0, True)
79    node.width = getDimension(u"width", node.base.width + node.leftpadding, True)
80    if node.width < 0: node.width = 0
81    node.leftspace = node.base.leftspace
82    node.rightspace = node.base.rightspace
83
84
85def measure_mfenced (node):
86    old_children = node.children
87    node.children = []
88
89    # Add fences and separators, and process as a mrow
90    openingFence = node.getProperty(u"open")
91    openingFence = " ".join(openingFence.split())
92    if len(openingFence) > 0:
93        opening = mathnode.MathNode (u"mo", {u"fence": u"true", u"form": u"prefix"},
94                                     None, node.config, node)
95        opening.text = openingFence
96        opening.measure()
97
98    separators = "".join(node.getProperty(u"separators").split())
99    sepindex = 0
100    lastsep = len(separators) - 1
101
102    for ch in old_children:
103        if len(node.children) > 1 and lastsep >= 0:
104            sep = mathnode.MathNode (u"mo", {u"separator": u"true", u"form": u"infix"},
105                                     None, node.config, node)
106            sep.text = separators[sepindex]
107            sep.measure()
108            sepindex  = min (sepindex+1, lastsep)
109        node.children.append(ch)
110
111    closingFence = node.getProperty(u"close")
112    closingFence = " ".join(closingFence.split())
113    if len(closingFence) > 0:
114        closing = mathnode.MathNode (u"mo", {u"fence": u"true", u"form": u"postfix"},
115                                     None, node.config, node)
116        closing.text = closingFence
117        closing.measure()
118
119    measure_mrow(node)
120
121def measure_mo (node):
122    # Normalize operator glyphs
123    # Use minus instead of hyphen
124    if node.hasGlyph(0x2212): node.text = node.text.replace(u'-', u'\u2212')
125    # Use prime instead of apostrophe
126    if node.hasGlyph(0x2032): node.text = node.text.replace(u'\'', u'\u2032')
127    # Invisible operators produce space nodes
128    if node.text in [u'\u2061', u'\u2062', u'\u2063']: node.isSpace = True
129    else: node.measureText()
130
131    # Align the operator along the mathematical axis for the respective font
132    node.alignToAxis = True
133    node.textShift = - node.axis()
134    node.height += node.textShift
135    node.ascender += node.textShift
136    node.depth -= node.textShift
137    node.descender -= node.textShift
138
139def measure_mn (node):
140    node.measureText()
141
142def measure_mi (node):
143    node.measureText()
144
145def measure_mtext (node):
146    node.measureText()
147    spacing = node.parseSpace(u"thinmathspace")
148    node.leftspace = spacing
149    node.rightspace = spacing
150
151def measure_merror (node):
152    createImplicitRow(node)
153
154    node.borderWidth = node.nominalLineWidth()
155    node.width =  node.base.width + 2 * node.borderWidth
156    node.height = node.base.height + node.borderWidth
157    node.depth = node.base.depth + node.borderWidth
158    node.ascender = node.base.ascender
159    node.descender = node.base.descender
160
161def measure_ms (node):
162    lq = node.getProperty(u"lquote")
163    rq = node.getProperty(u"rquote")
164    if lq: node.text = node.text.replace(lq, u"\\"+lq)
165    if rq and rq != lq: node.text = node.text.replace(rq, u"\\"+rq)
166    node.text = lq + node.text + rq
167    node.measureText()
168    spacing = node.parseSpace(u"thinmathspace")
169    node.leftspace = spacing
170    node.rightspace = spacing
171
172def measure_mspace (node):
173    node.height = node.parseLength(node.getProperty(u"height"))
174    node.depth = node.parseLength(node.getProperty(u"depth"))
175    node.width = node.parseSpace(node.getProperty(u"width"))
176
177    # Add ascender/descender values
178    node.ascender = node.nominalAscender()
179    node.descender = node.nominalDescender()
180
181def measure_mrow (node):
182    if len(node.children) == 0: return
183
184    # Determine alignment type for the row. If there is a non-axis-aligned,
185    # non-space child in the row, the whole row is non-axis-aligned. The row
186    # that consists of just spaces is considered a space itself
187    node.alignToAxis = True
188    node.isSpace = True
189    for ch in node.children:
190        if not ch.isSpace:
191            node.alignToAxis = node.alignToAxis and ch.alignToAxis
192            node.isSpace = False
193
194    # Process non-marking operators
195    for i in range(len(node.children)):
196        ch = node.children[i]
197        if ch.core.elementName != u'mo': continue
198        if ch.text in [u'\u2061', u'\u2062', u'\u2063']:
199            ch.text = u""
200            def longtext(n):
201                if n is None: return False
202                if n.isSpace: return False
203                if n.core.elementName == u"ms": return True
204                if n.core.elementName in [u"mo", u"mi", u"mtext"]: return (len(n.core.text) > 1)
205                return False
206            ch_prev = None; ch_next = None;
207            if i > 0: ch_prev = node.children[i-1]
208            if i + 1 < len(node.children): ch_next = node.children[i+1]
209            if longtext(ch_prev) or longtext(ch_next):
210                ch.width = ch.parseSpace("thinmathspace")
211
212    # Calculate extent for vertical stretching
213    (node.ascender, node.descender) = getVerticalStretchExtent(node.children, node.alignToAxis, node.axis())
214
215    # Grow sizeable operators
216    for ch in node.children:
217        if ch.core.stretchy:
218            desiredHeight = node.ascender; desiredDepth = node.descender
219            if ch.alignToAxis and not node.alignToAxis:
220                desiredHeight -= node.axis(); desiredDepth += node.axis()
221            desiredHeight -= ch.core.ascender - ch.core.height
222            desiredDepth -= ch.core.descender - ch.core.depth
223            stretch(ch, toHeight=desiredHeight,
224                        toDepth=desiredDepth,
225                        symmetric=node.alignToAxis)
226
227    # Recalculate height/depth after growing operators
228    (node.height, node.depth, node.ascender, node.descender) = getRowVerticalExtent(node.children, node.alignToAxis, node.axis())
229
230    # Finally, calculate width and spacings
231    for ch in node.children:
232        node.width += ch.width + ch.leftspace + ch.rightspace
233    node.leftspace = node.children[0].leftspace
234    node.rightspace = node.children[-1].rightspace
235    node.width -= node.leftspace + node.rightspace
236
237def measure_mfrac (node):
238    if len(node.children) != 2:
239        node.error("Invalid content of 'mfrac' element: element should have exactly two children")
240        if len(node.children) < 2:
241            measure_mrow (node); return
242
243    (node.enumerator, node.denominator) = node.children[:2]
244    node.alignToAxis = True
245
246    ruleWidthKeywords = {u"medium": "1",
247                         u"thin": "0.5",
248                         u"thick": "2"}
249
250    widthAttr = node.getProperty(u"linethickness")
251    widthAttr = ruleWidthKeywords.get(widthAttr, widthAttr)
252    unitWidth = node.nominalLineWidth()
253    node.ruleWidth = node.parseLength(widthAttr, unitWidth)
254
255    node.ruleGap = node.nominalLineGap()
256    if node.tightspaces: node.ruleGap /= 1.41 # more compact style if in scripts/limits
257
258    if node.getProperty(u"bevelled") == u"true":
259        eh = node.enumerator.height + node.enumerator.depth
260        dh = node.denominator.height + node.denominator.depth
261        vshift = min (eh, dh) / 2
262        node.height = (eh + dh - vshift) / 2
263        node.depth = node.height
264
265        node.slope = defaultSlope
266        node.width = node.enumerator.width + node.denominator.width
267        node.width += vshift / node.slope
268        node.width += (node.ruleWidth + node.ruleGap) * math.sqrt(1 + node.slope**2)
269        node.leftspace = node.enumerator.leftspace
270        node.rightspace = node.denominator.rightspace
271    else:
272        node.height = node.ruleWidth / 2 + node.ruleGap + node.enumerator.height + node.enumerator.depth
273        node.depth  = node.ruleWidth / 2 + node.ruleGap + node.denominator.height + node.denominator.depth
274        node.width = max(node.enumerator.width, node.denominator.width) + 2 * node.ruleWidth
275        node.leftspace = node.ruleWidth
276        node.rightspace = node.ruleWidth
277
278    node.ascender = node.height
279    node.descender = node.depth
280
281def measure_msqrt(node):
282    # Create an explicit mrow if there's more than one child
283    createImplicitRow(node)
284    enclosures.addRadicalEnclosure(node)
285
286def measure_mroot(node):
287    if len(node.children) != 2:
288        node.error("Invalid content of 'mroot' element: element should have exactly two children")
289
290    if len(node.children) < 2:
291        node.rootindex = None
292        measure_msqrt(node)
293    else:
294        setNodeBase(node, node.children[0])
295        node.rootindex = node.children[1]
296        enclosures.addRadicalEnclosure(node)
297        node.width += max(0, node.rootindex.width - node.cornerWidth)
298        node.height += max(0, node.rootindex.height + node.rootindex.depth - node.cornerHeight)
299        node.ascender = node.height
300
301def measure_msub (node):
302    if len(node.children) != 2:
303        node.error("Invalid content of 'msub' element: element should have exactly two children")
304        if len(node.children) < 2:
305            measure_mrow (node); return
306    measureScripts(node, [node.children[1]], None)
307
308def measure_msup (node):
309    if len(node.children) != 2:
310        node.error("Invalid content of 'msup' element: element should have exactly two children")
311        if len(node.children) < 2:
312            measure_mrow (node); return
313    measureScripts(node, None, [node.children[1]])
314
315def measure_msubsup (node):
316    if len(node.children) != 3:
317        node.error("Invalid content of 'msubsup' element: element should have exactly three children")
318        if len(node.children) == 2:
319            measure_msub (node); return
320        elif len(node.children) < 2:
321            measure_mrow (node); return
322    measureScripts(node, [node.children[1]], [node.children[2]])
323
324def measure_munder (node):
325    if len(node.children) != 2:
326        node.error("Invalid content of 'munder' element: element should have exactly two children")
327        if len(node.children) < 2:
328            measure_mrow (node); return
329    measureLimits(node, node.children[1], None)
330
331
332def measure_mover (node):
333    if len(node.children) != 2:
334        node.error("Invalid content of 'mover' element: element should have exactly two children")
335        if len(node.children) < 2:
336            measure_mrow (node); return
337    measureLimits(node, None, node.children[1])
338
339def measure_munderover (node):
340    if len(node.children) != 3:
341        node.error("Invalid content of 'munderover' element: element should have exactly three children")
342        if len(node.children) == 2:
343            measure_munder (node); return
344        elif len(node.children) < 2:
345            measure_mrow (node); return
346    measureLimits(node, node.children[1], node.children[2])
347
348
349def measure_mmultiscripts (node):
350    if len(node.children) == 0:
351        measure_mrow (node); return
352
353    # Sort children into sub- and superscripts
354    subscripts = []
355    superscripts = []
356    presubscripts = []
357    presuperscripts = []
358
359    isPre = False; isSub = True
360    for ch in node.children[1:]:
361        if ch.elementName == u"mprescripts":
362            if isPre:
363                node.error("Repeated 'mprescripts' element inside 'mmultiscripts\n")
364            isPre = True; isSub = True; continue
365        if isSub:
366            if isPre: presubscripts.append(ch)
367            else: subscripts.append(ch)
368        else:
369            if isPre: presuperscripts.append(ch)
370            else: superscripts.append(ch)
371        isSub = not isSub
372
373    measureScripts(node, subscripts, superscripts, presubscripts, presuperscripts)
374
375def measure_menclose (node):
376    def pushEnclosure():
377        if node.decoration is None: return  # no need to push
378
379        wrapChildren (node, u"menclose")
380        setNodeBase(node.children[0], node.base)
381        setNodeBase(node, node.children[0])
382        node.base.decoration = node.decoration
383        node.base.decorationData = node.decorationData
384        node.decoration = None
385        node.decorationData = None
386        node.base.width = node.width
387        node.base.height = node.height
388        node.base.depth = node.depth
389        node.base.borderWidth = node.borderWidth
390
391    createImplicitRow(node)
392    signs = node.getProperty(u"notation").split()
393    node.width = node.base.width
394    node.height = node.base.height
395    node.depth = node.base.depth
396    node.decoration = None
397    node.decorationData = None
398    node.borderWidth = node.nominalLineWidth()
399    node.hdelta = node.nominalLineGap() + node.borderWidth
400    node.vdelta = node.nominalLineGap() + node.borderWidth
401
402    # Radical sign - convert to msqrt for simplicity
403    if u"radical" in signs:
404        wrapChildren(node, u"msqrt")
405        setNodeBase(node.children[0], node.base)
406        setNodeBase(node, node.children[0])
407        node.base.makeContext()
408        node.base.measureNode()
409        node.width = node.base.width
410        node.height = node.base.height
411        node.depth = node.base.depth
412
413    # Strikes
414    strikes = [ u"horizontalstrike" in signs,
415                u"verticalstrike" in signs,
416                u"updiagonalstrike" in signs,
417                u"downdiagonalstrike" in signs ]
418    if True in strikes:
419        pushEnclosure()
420        node.decoration = "strikes"
421        node.decorationData = strikes
422        # no size change - really?
423
424    # Rounded box
425    if u"roundedbox" in signs:
426        pushEnclosure()
427        node.decoration = "roundedbox"
428        enclosures.addBoxEnclosure(node)
429
430    # Square box
431    if u"box" in signs:
432        pushEnclosure()
433        node.decoration = "box"
434        enclosures.addBoxEnclosure(node)
435
436    # Circle
437    if u"circle" in signs:
438        pushEnclosure()
439        node.decoration = "circle"
440        enclosures.addCircleEnclosure(node)
441
442    # Borders
443    borders = [ u"left" in signs,
444                u"right" in signs,
445                u"top" in signs,
446                u"bottom" in signs ]
447    if True in borders:
448        pushEnclosure()
449        if False in borders:
450            node.decoration = "borders"
451            enclosures.addBorderEnclosure(node, borders)
452        else:
453            node.decoration = "box"
454            enclosures.addBoxEnclosure(node)
455
456    # Long division
457    if u"longdiv" in signs:
458        pushEnclosure()
459        node.decoration = "borders"
460        enclosures.addBorderEnclosure(node, [True, False, True, False]) # left top for now
461
462    # Actuarial
463    if u"actuarial" in signs:
464        pushEnclosure()
465        node.decoration = "borders"
466        enclosures.addBorderEnclosure(node, [False, True, True, False]) # right top
467
468def measure_mtable (node):
469    node.lineWidth = node.nominalLineWidth()
470
471    # For readability, most layout stuff is split into pieces and moved to tables.py
472    tables.arrangeCells(node)
473    tables.arrangeLines(node)
474
475    # Calculate column widths
476    tables.calculateColumnWidths(node)
477    # Expand stretchy operators horizontally
478    for r in node.rows:
479        for i in range(len(r.cells)):
480            c = r.cells[i]
481            if c is None or c.content is None: continue
482            content = c.content
483            if content.elementName == u"mtd":
484                if len(content.children) != 1: continue
485                content = content.children[0]
486                if content.core.stretchy: c.content = content
487            if content.core.stretchy:
488                if c.colspan == 1:
489                    stretch(content, toWidth = node.columns[i].width)
490                else:
491                    spannedColumns = node.columns[i : i + c.colspan]
492                    cellSize = sum([x.width for x in spannedColumns])
493                    cellSize += sum([x.spaceAfter for x in spannedColumns[:-1]])
494                    stretch(content, toWidth = cellSize)
495
496    # Calculate row heights
497    tables.calculateRowHeights(node)
498    # Expand stretchy operators vertically in all cells
499    for i in range(len(node.rows)):
500        r = node.rows[i]
501        for c in r.cells:
502            if c is None or c.content is None: continue
503            content = c.content
504            if content.elementName == u"mtd":
505                if len(content.children) != 1: continue
506                content = content.children[0]
507                if content.core.stretchy: c.content = content
508            if content.core.stretchy:
509                if c.rowspan == 1:
510                    stretch(content, toHeight = r.height - c.vshift,
511                                     toDepth = r.depth + c.vshift)
512                else:
513                    spannedRows = node.rows[i : i + c.rowspan]
514                    cellSize = sum([x.height + x.depth for x in spannedRows])
515                    cellSize += sum([x.spaceAfter for x in spannedRows[:-1]])
516                    stretch(content, toHeight = cellSize / 2,
517                                     toDepth = cellSize / 2)
518
519    # Recalculate widths, to account for stretched cells
520    tables.calculateColumnWidths(node)
521
522    # Calculate total width of the table
523    node.width  = sum([x.width + x.spaceAfter for x in node.columns])
524    node.width += 2 * node.framespacings[0]
525
526    # Calculate total height of the table
527    vsize = sum([x.height + x.depth + x.spaceAfter for x in node.rows])
528    vsize += 2 * node.framespacings[1]
529
530    # Calculate alignment point
531    (alignType, alignRow) = tables.getAlign(node)
532
533    if alignRow is None:
534       topLine = 0
535       bottomLine = vsize
536       axisLine = vsize / 2
537       baseLine = axisLine + node.axis()
538    else:
539       row = node.rows[alignRow - 1]
540       topLine = node.framespacings[1]
541       for r in node.rows[0 : alignRow]: topLine += r.height + r.depth + r.spaceAfter
542       bottomLine = topLine + row.height + row.depth
543       if row.alignToAxis:
544          axisLine = topLine + row.height
545          baseLine = axisLine + node.axis()
546       else:
547          baseLine = topLine + row.height
548          axisLine = baseLine - node.axis()
549
550    if alignType == u"axis":
551        node.alignToAxis = True
552        node.height = axisLine
553    elif alignType == u"baseline":
554        node.alignToAxis = False
555        node.height = baseLine
556    elif alignType == u"center":
557        node.alignToAxis = False
558        node.height = (topLine + bottomLine) / 2
559    elif alignType == u"top":
560        node.alignToAxis = False
561        node.height = topLine
562    elif alignType == u"bottom":
563        node.alignToAxis = False
564        node.height = bottomLine
565    else:
566        node.error("Unrecognized or unsupported table alignment value: " + alignType)
567        node.alignToAxis = True
568        node.height = axisLine
569    node.depth = vsize - node.height
570
571    node.ascender = node.height
572    node.descender = node.depth
573
574def measure_mtr (node):
575    if node.parent is None or node.parent.elementName != u"mtable":
576        node.error("Misplaced '%s' element: should be child of 'mtable'" % node.elementName)
577    pass # all processing is done on the table
578
579def measure_mlabeledtr (node):
580    # Strip the label and treat as an ordinary 'mtr'
581    if len(node.children) == 0:
582        node.error("Missing label in '%s' element" % node.elementName)
583    else:
584        node.warning("MathML element '%s' is unsupported: label omitted" % node.elementName)
585        node.children = node.children[1:]
586    measure_mtr(node)
587
588def measure_mtd (node):
589    if node.parent is None or node.parent.elementName not in [u"mtr", u"mlabeledtr", u"mtable"]:
590        node.error("Misplaced '%s' element: should be child of 'mtr', 'mlabeledtr', or 'mtable'" % node.elementName)
591    measure_mrow(node)
592
593def measureScripts(node, subscripts, superscripts,
594                         presubscripts = None, presuperscripts = None):
595    node.subscripts = subscripts or []
596    node.superscripts = superscripts or []
597    node.presubscripts = presubscripts or []
598    node.presuperscripts = presuperscripts or []
599
600    setNodeBase(node, node.children[0])
601    node.width = node.base.width
602    node.height = node.base.height
603    node.depth = node.base.depth
604    node.ascender = node.base.ascender
605    node.descender = node.base.descender
606
607    subs = node.subscripts + node.presubscripts
608    supers = node.superscripts + node.presuperscripts
609    node.subscriptAxis = max([0] + [x.axis() for x in subs])
610    node.superscriptAxis = max([0] + [x.axis() for x in supers])
611    gap = max ([x.nominalLineGap() for x in subs+supers])
612    protrusion = node.parseLength("0.25ex")
613    scriptMedian = node.axis()
614
615    (subHeight, subDepth, subAscender, subDescender) = getRowVerticalExtent(subs, False, node.subscriptAxis)
616    (superHeight, superDepth, superAscender, superDescender) = getRowVerticalExtent(supers, False, node.superscriptAxis)
617
618    node.subShift = 0
619    if len(subs) > 0:
620        shiftAttr = node.getProperty(u"subscriptshift")
621        if shiftAttr is None: shiftAttr = "0.5ex"
622        node.subShift = node.parseLength(shiftAttr)  # positive shifts down
623        node.subShift = max (node.subShift, subHeight - scriptMedian + gap)
624        if node.alignToAxis: node.subShift += node.axis()
625        node.subShift = max (node.subShift, node.base.depth + protrusion - subDepth)
626        node.height = max (node.height, subHeight - node.subShift)
627        node.depth = max (node.depth, subDepth + node.subShift)
628        node.ascender = max (node.ascender, subAscender - node.subShift)
629        node.descender = max (node.descender, subDescender + node.subShift)
630
631    node.superShift = 0
632    if len(supers) > 0:
633        shiftAttr = node.getProperty(u"superscriptshift")
634        if shiftAttr is None: shiftAttr = "1ex"
635        node.superShift = node.parseLength(shiftAttr)  # positive shifts up
636        node.superShift = max (node.superShift, superDepth + scriptMedian + gap)
637        if node.alignToAxis: node.superShift -= node.axis()
638        node.superShift = max (node.superShift, node.base.height + protrusion - superHeight)
639        node.height = max (node.height, superHeight + node.superShift)
640        node.depth = max (node.depth, superDepth - node.superShift)
641        node.ascender = max (node.ascender, superHeight + node.superShift)
642        node.descender = max (node.descender, superDepth - node.superShift)
643
644    def parallelWidths (nodes1, nodes2):
645       widths = []
646       for i in range (max(len(nodes1), len(nodes2))):
647          w = 0
648          if i < len(nodes1): w = max (w, nodes1[i].width)
649          if i < len(nodes2): w = max (w, nodes2[i].width)
650          widths.append(w)
651       return widths
652
653    node.postwidths = parallelWidths(node.subscripts, node.superscripts)
654    node.prewidths = parallelWidths(node.presubscripts, node.presuperscripts)
655    node.width += sum(node.prewidths + node.postwidths)
656
657def measureLimits(node, underscript, overscript):
658    if node.children[0].core.moveLimits:
659        subs = []
660        supers = []
661        if underscript is not None: subs = [underscript]
662        if overscript is not None: supers = [overscript]
663        measureScripts(node, subs, supers); return
664
665    node.underscript = underscript
666    node.overscript = overscript
667    setNodeBase(node, node.children[0])
668
669    node.width = node.base.width
670    if overscript is not None: node.width = max(node.width, overscript.width)
671    if underscript is not None: node.width = max(node.width, underscript.width)
672    stretch (node.base, toWidth = node.width)
673    stretch (overscript, toWidth = node.width)
674    stretch (underscript, toWidth = node.width)
675
676    gap = node.nominalLineGap()
677
678    if overscript is not None:
679        overscriptBaselineHeight = node.base.height + gap + overscript.depth
680        node.height =  overscriptBaselineHeight + overscript.height
681        node.ascender = node.height
682    else:
683        node.height = node.base.height
684        node.ascender = node.base.ascender
685
686    if underscript is not None:
687        underscriptBaselineDepth = node.base.depth + gap + underscript.height
688        node.depth =  underscriptBaselineDepth + underscript.depth
689        node.descender = node.depth
690    else:
691        node.depth = node.base.depth
692        node.descender = node.base.descender
693
694def stretch(node, toWidth=None, toHeight=None, toDepth=None, symmetric=False):
695    if node is None: return
696    if not node.core.stretchy: return
697    if node is not node.base:
698        if toWidth is not None:
699            toWidth -= node.width - node.base.width
700        stretch(node.base, toWidth, toHeight, toDepth, symmetric)
701        node.measureNode()
702    elif node.elementName == u"mo":
703        if node.fontSize == 0: return
704
705        maxsizedefault = node.opdefaults.get(u"maxsize")
706        maxsizeattr = node.getProperty(u"maxsize", maxsizedefault)
707        if (maxsizeattr == u"infinity"):
708            maxScale = None
709        else:
710            maxScale = node.parseSpaceOrPercent(maxsizeattr, node.fontSize, node.fontSize) / node.fontSize
711
712        minsizedefault = node.opdefaults.get(u"minsize")
713        minsizeattr = node.getProperty(u"minsize", minsizedefault)
714        minScale = node.parseSpaceOrPercent(minsizeattr, node.fontSize, node.fontSize) / node.fontSize
715        if toWidth is None:
716            stretchVertically(node, toHeight, toDepth, minScale, maxScale, symmetric)
717        else:
718            stretchHorizontally(node, toWidth, minScale, maxScale)
719
720def stretchVertically(node, toHeight, toDepth, minScale, maxScale, symmetric):
721    if node.ascender + node.descender == 0: return
722    if node.scaling == u"horizontal": return
723
724    if symmetric and node.symmetric:
725        toHeight = (toHeight + toDepth) / 2
726        toDepth = toHeight
727    scale = (toHeight + toDepth) / (node.ascender + node.descender)
728
729    if minScale: scale = max (scale, minScale)
730    if maxScale: scale = min (scale, maxScale)
731
732    node.fontSize *= scale
733    node.height *= scale
734    node.depth *= scale
735    node.ascender *= scale
736    node.descender *= scale
737    node.textShift *= scale
738
739    extraShift = ((toHeight - node.ascender) -
740                  (toDepth - node.descender)) / 2
741    node.textShift += extraShift
742    node.height += extraShift
743    node.ascender += extraShift
744    node.depth -= extraShift
745    node.descender -= extraShift
746
747    if node.scaling == u"vertical":
748        node.textStretch /= scale
749    else:
750        node.width *= scale
751        node.leftBearing *= scale
752        node.rightBearing *= scale
753
754def stretchHorizontally(node, toWidth, minScale, maxScale):
755    if node.width == 0: return
756    if node.scaling != u"horizontal": return
757
758    scale = toWidth / node.width
759    scale = max (scale, minScale)
760    if maxScale: scale = min (scale, maxScale)
761
762    node.width *= scale
763    node.textStretch *= scale
764    node.leftBearing *= scale
765    node.rightBearing *= scale
766
767def setNodeBase(node, base):
768     node.base = base
769     node.core = base.core
770     node.alignToAxis = base.alignToAxis
771     node.stretchy = base.stretchy
772
773def wrapChildren(node, wrapperElement):
774    old_children = node.children
775    node.children = []
776    base = mathnode.MathNode (wrapperElement, {}, None, node.config, node)
777    base.children = old_children
778
779def createImplicitRow(node):
780    if len(node.children) != 1:
781        wrapChildren(node, u"mrow")
782        node.children[0].makeContext();
783        node.children[0].measureNode();
784    setNodeBase(node, node.children[0])
785
786def getVerticalStretchExtent(descendants, rowAlignToAxis, axis):
787    ascender = 0; descender = 0
788    for ch in descendants:
789        if ch.core.stretchy:
790            asc = ch.core.ascender;  desc = ch.core.descender # 'cos it will grow
791        else:
792            asc = ch.ascender;  desc = ch.descender
793        if ch.alignToAxis and not rowAlignToAxis:
794            asc += axis; desc -= axis
795        elif not ch.alignToAxis and rowAlignToAxis:
796            chaxis = ch.axis()
797            asc -= chaxis; desc += chaxis
798        ascender = max (asc, ascender);  descender = max (desc, descender)
799    return (ascender, descender)
800
801
802def getRowVerticalExtent(descendants, rowAlignToAxis, axis):
803    height = 0; depth = 0; ascender = 0; descender = 0
804    for ch in descendants:
805        h = ch.height;  d = ch.depth;  asc = ch.ascender; desc = ch.descender
806        if ch.alignToAxis and not rowAlignToAxis:
807            h += axis; asc += axis; d -= axis; desc -= axis
808        elif not ch.alignToAxis and rowAlignToAxis:
809            chaxis = ch.axis()
810            h -= chaxis; asc -= chaxis; d += chaxis; desc += chaxis
811        height = max (h, height);  depth = max (d, depth)
812        ascender = max (asc, ascender);  descender = max (desc, descender)
813    return (height, depth, ascender, descender)
814