WebCore/mathml/RenderMathMLOperator.cpp
changeset 0 4f2f89ce4247
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebCore/mathml/RenderMathMLOperator.cpp	Fri Sep 17 09:02:29 2010 +0300
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2010 Alex Milowski (alex@milowski.com). All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#if ENABLE(MATHML)
+
+#include "RenderMathMLOperator.h"
+
+#include "FontSelector.h"
+#include "MathMLNames.h"
+#include "RenderText.h"
+
+namespace WebCore {
+    
+using namespace MathMLNames;
+
+RenderMathMLOperator::RenderMathMLOperator(Node* container)
+    : RenderMathMLBlock(container),
+      m_stretchHeight(0),
+      m_operator(0)
+{
+}
+    
+RenderMathMLOperator::RenderMathMLOperator(Node* container, UChar operatorChar)
+    : RenderMathMLBlock(container),
+      m_stretchHeight(0),
+      m_operator(operatorChar)
+{
+}
+    
+bool RenderMathMLOperator::isChildAllowed(RenderObject*, RenderStyle*) const
+{
+    return false;
+}
+
+static const float gOperatorSpacer = 0.1;
+static const float gOperatorExpansion = 1.2;
+
+void  RenderMathMLOperator::stretchToHeight(int height)
+{
+    if (height == m_stretchHeight)
+        return;
+    m_stretchHeight = static_cast<int>(height * gOperatorExpansion);
+    
+    updateBoxModelInfoFromStyle();
+    setNeedsLayoutAndPrefWidthsRecalc();
+    markContainingBlocksForLayout();
+}
+
+void RenderMathMLOperator::layout() 
+{
+    // FIXME: This probably shouldn't be called here but when the operator
+    // isn't stretched (e.g. outside of a mrow), it needs to be called somehow
+    updateFromElement();
+    RenderBlock::layout();
+}
+
+// This is a table of stretchy characters.
+// FIXME: Should this be read from the unicode characteristics somehow?
+// table:  stretchy operator, top char, extension char, bottom char, middle char
+static struct StretchyCharacter {
+    UChar character;
+    UChar topGlyph;
+    UChar extensionGlyph;
+    UChar bottomGlyph;
+    UChar middleGlyph;
+} stretchyCharacters[9] = {
+{ 0x28  , 0x239b, 0x239c, 0x239d, 0x0    }, // left parenthesis
+{ 0x29  , 0x239e, 0x239f, 0x23a0, 0x0    }, // right parenthesis
+{ 0x5b  , 0x23a1, 0x23a2, 0x23a3, 0x0    }, // left square bracket
+{ 0x5d  , 0x23a4, 0x23a5, 0x23a6, 0x0    }, // right square bracket
+{ 0x7b  , 0x23a7, 0x23aa, 0x23a9, 0x23a8 }, // left curly bracket
+{ 0x7c  , 0x23d0, 0x23d0, 0x23d0, 0x0    }, // vertical bar
+{ 0x7d  , 0x23ab, 0x23aa, 0x23ad, 0x23ac }, // right curly bracket
+{ 0x222b, 0x2320, 0x23ae, 0x2321, 0x0    } // integral sign
+};
+
+// We stack glyphs using a 14px height with a displayed glyph height
+// of 10px.  The line height is set to less than the 14px so that there
+// are no blank spaces between the stacked glyphs.
+//
+// Certain glyphs (e.g. middle and bottom) need to be adjusted upwards
+// in the stack so that there isn't a gap.
+//
+// All of these settings are represented in the constants below.
+
+static const int gGlyphFontSize = 14;
+static const int gGlyphLineHeight = 12;
+static const int gMinimumStretchHeight = 24;
+static const int gGlyphHeight = 10;
+static const int gMiddleGlyphTopAdjust = -2;
+static const int gBottomGlyphTopAdjust = -4;
+static const float gMinimumRatioForStretch = 0.10;
+
+void RenderMathMLOperator::updateFromElement()
+{
+    // clear our children
+    while (firstChild()) {
+       RenderObject* obj = firstChild();
+       removeChild(obj);
+    }
+    
+    // If the operator is fixed, it will be contained in m_operator
+    UChar firstChar = m_operator;
+    
+    // This boolean indicates whether stretching is disabled via the markup.
+    bool stretchDisabled = false;
+    
+    // We made need the element later if we can't stretch.
+    if (node()->nodeType() == Node::ELEMENT_NODE) {
+        if (Element* mo = static_cast<Element*>(node())) {
+            AtomicString stretchyAttr = mo->getAttribute(MathMLNames::stretchyAttr);
+            stretchDisabled = equalIgnoringCase(stretchyAttr, "false");
+            
+            // If stretching isn't disabled, get the character from the text content.
+            if (!stretchDisabled && !firstChar) {
+                String opText = mo->textContent();
+                for (unsigned int i = 0; !firstChar && i < opText.length(); i++) {
+                    if (!isSpaceOrNewline(opText[i]))
+                       firstChar = opText[i];
+                }
+            }
+        }
+    }
+    
+    // The 'index' holds the stretchable character's glyph information
+    int index = -1;
+    
+    // isStretchy indicates whether the character is streatchable via a number of factors.
+    bool isStretchy = false;
+    
+    // Check for a stretchable character.
+    if (!stretchDisabled && firstChar) {
+        const int maxIndex = sizeof(stretchyCharacters) / sizeof(stretchyCharacters[0]);
+        for (index++; index < maxIndex; index++) {
+            if (stretchyCharacters[index].character == firstChar) {
+                isStretchy = true;
+                break;
+            }
+        }
+    }
+    
+    // We only stretch character if the stretch height is larger than a minimum size (e.g. 24px).
+    bool shouldStretch = isStretchy && m_stretchHeight>gMinimumStretchHeight;
+    m_isCentered = true;
+    
+    // Either stretch is disabled or we don't have a stretchable character over the minimum height
+    if (stretchDisabled || !shouldStretch) {
+        m_isStacked = false;
+        RenderBlock* container = new (renderArena()) RenderMathMLBlock(node());
+        
+        RefPtr<RenderStyle> newStyle = RenderStyle::create();
+        newStyle->inheritFrom(style());
+        newStyle->setDisplay(INLINE_BLOCK);
+
+        // Check for a stretchable character that is under the minimum height and use the
+        // font size to adjust the glyph size.
+        int currentFontSize = style()->fontSize();
+        if (!stretchDisabled && isStretchy && m_stretchHeight > 0 && m_stretchHeight <= gMinimumStretchHeight  && m_stretchHeight > currentFontSize) {
+            FontDescription* desc = new FontDescription();
+            desc->setIsAbsoluteSize(true);
+            desc->setSpecifiedSize(m_stretchHeight);
+            desc->setComputedSize(m_stretchHeight);
+            newStyle->setFontDescription(*desc);
+            newStyle->font().update(newStyle->font().fontSelector());
+            newStyle->setVerticalAlign(BASELINE);
+            m_isCentered = false;
+        } else {
+            int topPad = (m_stretchHeight - currentFontSize) / 2;
+            
+            if (topPad / static_cast<float>(m_stretchHeight) > gMinimumRatioForStretch) {
+                newStyle->setVerticalAlign(TOP);
+                newStyle->setPaddingTop(Length(topPad, Fixed));
+            } else {
+                m_isCentered = false;
+                newStyle->setVerticalAlign(BASELINE);
+            }
+        }
+        
+        container->setStyle(newStyle.release());
+        addChild(container);
+     
+        // Build the text of the operator.  
+        RenderText* text = 0;
+        if (m_operator) 
+            text = new (renderArena()) RenderText(node(), StringImpl::create(&m_operator, 1));
+        else if (node()->nodeType() == Node::ELEMENT_NODE)
+            if (Element* mo = static_cast<Element*>(node()))
+                text = new (renderArena()) RenderText(node(), StringImpl::create(mo->textContent().characters(), mo->textContent().length()));
+        // If we can't figure out the text, leave it blank.
+        if (text) {
+            RefPtr<RenderStyle> textStyle = RenderStyle::create();
+            textStyle->inheritFrom(container->style());
+            text->setStyle(textStyle.release());
+            container->addChild(text);
+        }
+    } else {
+        // Build stretchable characters as a stack of glyphs.
+        m_isStacked = true;
+        
+        if (stretchyCharacters[index].middleGlyph) {
+            // We have a middle glyph (e.g. a curly bracket) that requires special processing.
+            int half = (m_stretchHeight - gGlyphHeight) / 2;
+            if (half <= gGlyphHeight) {
+                // We only have enough space for a single middle glyph.
+                createGlyph(stretchyCharacters[index].topGlyph, half);
+                createGlyph(stretchyCharacters[index].middleGlyph, gGlyphHeight, gMiddleGlyphTopAdjust);
+                createGlyph(stretchyCharacters[index].bottomGlyph, 0, gBottomGlyphTopAdjust);
+            } else {
+                // We have to extend both the top and bottom to the middle.
+                createGlyph(stretchyCharacters[index].topGlyph, gGlyphHeight);
+                int remaining = half - gGlyphHeight;
+                while (remaining > 0) {
+                    if (remaining < gGlyphHeight) {
+                        createGlyph(stretchyCharacters[index].extensionGlyph, remaining);
+                        remaining = 0;
+                    } else {
+                        createGlyph(stretchyCharacters[index].extensionGlyph, gGlyphHeight);
+                        remaining -= gGlyphHeight;
+                    }
+                }
+                
+                // The middle glyph in the stack.
+                createGlyph(stretchyCharacters[index].middleGlyph, gGlyphHeight, gMiddleGlyphTopAdjust);
+                
+                // The remaining is the top half minus the middle glyph height.
+                remaining = half - gGlyphHeight;
+                // We need to make sure we have the full height in case the height is odd.
+                if (m_stretchHeight % 2 == 1)
+                    remaining++;
+                
+                // Extend to the bottom glyph.
+                while (remaining > 0) {
+                    if (remaining < gGlyphHeight) {
+                        createGlyph(stretchyCharacters[index].extensionGlyph, remaining);
+                        remaining = 0;
+                    } else {
+                        createGlyph(stretchyCharacters[index].extensionGlyph, gGlyphHeight);
+                        remaining -= gGlyphHeight;
+                    }
+                }
+                
+                // The bottom glyph in the stack.
+                createGlyph(stretchyCharacters[index].bottomGlyph, 0, gBottomGlyphTopAdjust);
+            }
+        } else {
+            // We do not have a middle glyph and so we just extend from the top to the bottom glyph.
+            int remaining = m_stretchHeight - 2 * gGlyphHeight;
+            createGlyph(stretchyCharacters[index].topGlyph, gGlyphHeight);
+            while (remaining > 0) {
+                if (remaining < gGlyphHeight) {
+                    createGlyph(stretchyCharacters[index].extensionGlyph, remaining);
+                    remaining = 0;
+                } else {
+                    createGlyph(stretchyCharacters[index].extensionGlyph, gGlyphHeight);
+                    remaining -= gGlyphHeight;
+                }
+            }
+            createGlyph(stretchyCharacters[index].bottomGlyph, 0, gBottomGlyphTopAdjust);
+        }
+    }
+}
+
+RefPtr<RenderStyle> RenderMathMLOperator::createStackableStyle(int size, int topRelative)
+{
+    RefPtr<RenderStyle> newStyle = RenderStyle::create();
+    newStyle->inheritFrom(style());
+    newStyle->setDisplay(BLOCK);
+
+    FontDescription* desc = new FontDescription();
+    desc->setIsAbsoluteSize(true);
+    desc->setSpecifiedSize(gGlyphFontSize);
+    desc->setComputedSize(gGlyphFontSize);
+    newStyle->setFontDescription(*desc);
+    newStyle->font().update(newStyle->font().fontSelector());
+    newStyle->setLineHeight(Length(gGlyphLineHeight, Fixed));
+    newStyle->setVerticalAlign(TOP);
+    
+    if (size > 0)
+        newStyle->setMaxHeight(Length(size, Fixed));
+        
+    newStyle->setOverflowY(OHIDDEN);
+    newStyle->setOverflowX(OHIDDEN);
+    if (topRelative) {
+        newStyle->setTop(Length(topRelative, Fixed));
+        newStyle->setPosition(RelativePosition);
+    }
+    
+    return newStyle;
+}
+
+RenderBlock* RenderMathMLOperator::createGlyph(UChar glyph, int size, int charRelative, int topRelative)
+{
+    RenderBlock* container = new (renderArena()) RenderMathMLBlock(node());
+    container->setStyle(createStackableStyle(size, topRelative).release());
+    addChild(container);
+    RenderBlock* parent = container;
+    if (charRelative) {
+        RenderBlock* charBlock = new (renderArena()) RenderBlock(node());
+        RefPtr<RenderStyle> charStyle = RenderStyle::create();
+        charStyle->inheritFrom(container->style());
+        charStyle->setDisplay(INLINE_BLOCK);
+        charStyle->setTop(Length(charRelative, Fixed));
+        charStyle->setPosition(RelativePosition);
+        charBlock->setStyle(charStyle);
+        parent->addChild(charBlock);
+        parent = charBlock;
+    }
+     
+    RenderText* text = new (renderArena()) RenderText(node(), StringImpl::create(&glyph, 1));
+    text->setStyle(container->style());
+    parent->addChild(text);
+    return container;
+}
+
+int RenderMathMLOperator::baselinePosition(bool firstLine, bool isRootLineBox) const
+{
+    if (m_isStacked)
+        return m_stretchHeight * 2 / 3 - (m_stretchHeight - static_cast<int>(m_stretchHeight / gOperatorExpansion)) / 2;
+    if (m_isCentered && firstChild()) 
+        return firstChild()->baselinePosition(firstLine, isRootLineBox);
+    return RenderBlock::baselinePosition(firstLine, isRootLineBox);
+}
+
+}
+
+#endif