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