Home | History | Annotate | Download | only in mathml
      1 /*
      2  * Copyright (C) 2010 Alex Milowski (alex (at) milowski.com). All rights reserved.
      3  * Copyright (C) 2010 Franois Sausset (sausset (at) gmail.com). All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions
      7  * are met:
      8  * 1. Redistributions of source code must retain the above copyright
      9  *    notice, this list of conditions and the following disclaimer.
     10  * 2. Redistributions in binary form must reproduce the above copyright
     11  *    notice, this list of conditions and the following disclaimer in the
     12  *    documentation and/or other materials provided with the distribution.
     13  *
     14  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     15  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     16  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     17  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     18  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     19  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     20  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     21  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     22  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     25  */
     26 
     27 #include "config.h"
     28 
     29 #if ENABLE(MATHML)
     30 
     31 #include "RenderMathMLOperator.h"
     32 
     33 #include "FontSelector.h"
     34 #include "MathMLNames.h"
     35 #include "RenderText.h"
     36 
     37 namespace WebCore {
     38 
     39 using namespace MathMLNames;
     40 
     41 RenderMathMLOperator::RenderMathMLOperator(Node* container)
     42     : RenderMathMLBlock(container)
     43     , m_stretchHeight(0)
     44     , m_operator(0)
     45 {
     46 }
     47 
     48 RenderMathMLOperator::RenderMathMLOperator(Node* container, UChar operatorChar)
     49     : RenderMathMLBlock(container)
     50     , m_stretchHeight(0)
     51     , m_operator(convertHyphenMinusToMinusSign(operatorChar))
     52 {
     53 }
     54 
     55 bool RenderMathMLOperator::isChildAllowed(RenderObject*, RenderStyle*) const
     56 {
     57     return false;
     58 }
     59 
     60 static const float gOperatorSpacer = 0.1f;
     61 static const float gOperatorExpansion = 1.2f;
     62 
     63 void RenderMathMLOperator::stretchToHeight(int height)
     64 {
     65     if (height == m_stretchHeight)
     66         return;
     67     m_stretchHeight = static_cast<int>(height * gOperatorExpansion);
     68 
     69     updateBoxModelInfoFromStyle();
     70     setNeedsLayout(true);
     71 }
     72 
     73 void RenderMathMLOperator::layout()
     74 {
     75     // FIXME: This probably shouldn't be called here but when the operator
     76     // isn't stretched (e.g. outside of a mrow), it needs to be called somehow
     77     updateFromElement();
     78     RenderBlock::layout();
     79 }
     80 
     81 // This is a table of stretchy characters.
     82 // FIXME: Should this be read from the unicode characteristics somehow?
     83 // table:  stretchy operator, top char, extension char, bottom char, middle char
     84 static struct StretchyCharacter {
     85     UChar character;
     86     UChar topGlyph;
     87     UChar extensionGlyph;
     88     UChar bottomGlyph;
     89     UChar middleGlyph;
     90 } stretchyCharacters[13] = {
     91     { 0x28  , 0x239b, 0x239c, 0x239d, 0x0    }, // left parenthesis
     92     { 0x29  , 0x239e, 0x239f, 0x23a0, 0x0    }, // right parenthesis
     93     { 0x5b  , 0x23a1, 0x23a2, 0x23a3, 0x0    }, // left square bracket
     94     { 0x2308, 0x23a1, 0x23a2, 0x23a2, 0x0    }, // left ceiling
     95     { 0x230a, 0x23a2, 0x23a2, 0x23a3, 0x0    }, // left floor
     96     { 0x5d  , 0x23a4, 0x23a5, 0x23a6, 0x0    }, // right square bracket
     97     { 0x2309, 0x23a4, 0x23a5, 0x23a5, 0x0    }, // right ceiling
     98     { 0x230b, 0x23a5, 0x23a5, 0x23a6, 0x0    }, // right floor
     99     { 0x7b  , 0x23a7, 0x23aa, 0x23a9, 0x23a8 }, // left curly bracket
    100     { 0x7c  , 0x23d0, 0x23d0, 0x23d0, 0x0    }, // vertical bar
    101     { 0x2016, 0x2016, 0x2016, 0x2016, 0x0    }, // double vertical line
    102     { 0x7d  , 0x23ab, 0x23aa, 0x23ad, 0x23ac }, // right curly bracket
    103     { 0x222b, 0x2320, 0x23ae, 0x2321, 0x0    } // integral sign
    104 };
    105 
    106 // We stack glyphs using a 14px height with a displayed glyph height
    107 // of 10px.  The line height is set to less than the 14px so that there
    108 // are no blank spaces between the stacked glyphs.
    109 //
    110 // Certain glyphs (e.g. middle and bottom) need to be adjusted upwards
    111 // in the stack so that there isn't a gap.
    112 //
    113 // All of these settings are represented in the constants below.
    114 
    115 // FIXME: use fractions of style()->fontSize() for proper zooming/resizing.
    116 static const int gGlyphFontSize = 14;
    117 static const int gGlyphLineHeight = 11;
    118 static const int gMinimumStretchHeight = 24;
    119 static const int gGlyphHeight = 10;
    120 static const int gTopGlyphTopAdjust = 1;
    121 static const int gMiddleGlyphTopAdjust = -1;
    122 static const int gBottomGlyphTopAdjust = -3;
    123 static const float gMinimumRatioForStretch = 0.10f;
    124 
    125 void RenderMathMLOperator::updateFromElement()
    126 {
    127     // Destroy our current children
    128     children()->destroyLeftoverChildren();
    129 
    130     // Since we share a node with our children, destroying our children will set our node's
    131     // renderer to 0, so we need to re-set it back to this.
    132     node()->setRenderer(this);
    133 
    134     // If the operator is fixed, it will be contained in m_operator
    135     UChar firstChar = m_operator;
    136 
    137     // This boolean indicates whether stretching is disabled via the markup.
    138     bool stretchDisabled = false;
    139 
    140     // We made need the element later if we can't stretch.
    141     if (node()->nodeType() == Node::ELEMENT_NODE) {
    142         if (Element* mo = static_cast<Element*>(node())) {
    143             AtomicString stretchyAttr = mo->getAttribute(MathMLNames::stretchyAttr);
    144             stretchDisabled = equalIgnoringCase(stretchyAttr, "false");
    145 
    146             // If stretching isn't disabled, get the character from the text content.
    147             if (!stretchDisabled && !firstChar) {
    148                 String opText = mo->textContent();
    149                 for (unsigned int i = 0; !firstChar && i < opText.length(); i++) {
    150                     if (!isSpaceOrNewline(opText[i]))
    151                         firstChar = opText[i];
    152                 }
    153             }
    154         }
    155     }
    156 
    157     // The 'index' holds the stretchable character's glyph information
    158     int index = -1;
    159 
    160     // isStretchy indicates whether the character is streatchable via a number of factors.
    161     bool isStretchy = false;
    162 
    163     // Check for a stretchable character.
    164     if (!stretchDisabled && firstChar) {
    165         const int maxIndex = WTF_ARRAY_LENGTH(stretchyCharacters);
    166         for (index++; index < maxIndex; index++) {
    167             if (stretchyCharacters[index].character == firstChar) {
    168                 isStretchy = true;
    169                 break;
    170             }
    171         }
    172     }
    173 
    174     // We only stretch character if the stretch height is larger than a minimum size (e.g. 24px).
    175     bool shouldStretch = isStretchy && m_stretchHeight>gMinimumStretchHeight;
    176 
    177     // Either stretch is disabled or we don't have a stretchable character over the minimum height
    178     if (stretchDisabled || !shouldStretch) {
    179         m_isStacked = false;
    180         RenderBlock* container = new (renderArena()) RenderMathMLBlock(node());
    181 
    182         RefPtr<RenderStyle> newStyle = RenderStyle::create();
    183         newStyle->inheritFrom(style());
    184         newStyle->setDisplay(INLINE_BLOCK);
    185         newStyle->setVerticalAlign(BASELINE);
    186 
    187         // Check for a stretchable character that is under the minimum height and use the
    188         // font size to adjust the glyph size.
    189         int currentFontSize = style()->fontSize();
    190         if (!stretchDisabled && isStretchy && m_stretchHeight > 0 && m_stretchHeight <= gMinimumStretchHeight  && m_stretchHeight > currentFontSize) {
    191             FontDescription desc;
    192             desc.setIsAbsoluteSize(true);
    193             desc.setSpecifiedSize(m_stretchHeight);
    194             desc.setComputedSize(m_stretchHeight);
    195             newStyle->setFontDescription(desc);
    196             newStyle->font().update(newStyle->font().fontSelector());
    197         }
    198 
    199         container->setStyle(newStyle.release());
    200         addChild(container);
    201 
    202         // Build the text of the operator.
    203         RenderText* text = 0;
    204         if (m_operator)
    205             text = new (renderArena()) RenderText(node(), StringImpl::create(&m_operator, 1));
    206         else if (node()->nodeType() == Node::ELEMENT_NODE)
    207             if (Element* mo = static_cast<Element*>(node()))
    208                 text = new (renderArena()) RenderText(node(), mo->textContent().replace(hyphenMinus, minusSign).impl());
    209         // If we can't figure out the text, leave it blank.
    210         if (text) {
    211             RefPtr<RenderStyle> textStyle = RenderStyle::create();
    212             textStyle->inheritFrom(container->style());
    213             text->setStyle(textStyle.release());
    214             container->addChild(text);
    215         }
    216     } else {
    217         // Build stretchable characters as a stack of glyphs.
    218         m_isStacked = true;
    219 
    220         if (stretchyCharacters[index].middleGlyph) {
    221             // We have a middle glyph (e.g. a curly bracket) that requires special processing.
    222             int half = (m_stretchHeight - gGlyphHeight) / 2;
    223             if (half <= gGlyphHeight) {
    224                 // We only have enough space for a single middle glyph.
    225                 createGlyph(stretchyCharacters[index].topGlyph, half, gTopGlyphTopAdjust);
    226                 createGlyph(stretchyCharacters[index].middleGlyph, gGlyphHeight, gMiddleGlyphTopAdjust);
    227                 createGlyph(stretchyCharacters[index].bottomGlyph, 0, gBottomGlyphTopAdjust);
    228             } else {
    229                 // We have to extend both the top and bottom to the middle.
    230                 createGlyph(stretchyCharacters[index].topGlyph, gGlyphHeight, gTopGlyphTopAdjust);
    231                 int remaining = half - gGlyphHeight;
    232                 while (remaining > 0) {
    233                     if (remaining < gGlyphHeight) {
    234                         createGlyph(stretchyCharacters[index].extensionGlyph, remaining);
    235                         remaining = 0;
    236                     } else {
    237                         createGlyph(stretchyCharacters[index].extensionGlyph, gGlyphHeight);
    238                         remaining -= gGlyphHeight;
    239                     }
    240                 }
    241 
    242                 // The middle glyph in the stack.
    243                 createGlyph(stretchyCharacters[index].middleGlyph, gGlyphHeight, gMiddleGlyphTopAdjust);
    244 
    245                 // The remaining is the top half minus the middle glyph height.
    246                 remaining = half - gGlyphHeight;
    247                 // We need to make sure we have the full height in case the height is odd.
    248                 if (m_stretchHeight % 2 == 1)
    249                     remaining++;
    250 
    251                 // Extend to the bottom glyph.
    252                 while (remaining > 0) {
    253                     if (remaining < gGlyphHeight) {
    254                         createGlyph(stretchyCharacters[index].extensionGlyph, remaining);
    255                         remaining = 0;
    256                     } else {
    257                         createGlyph(stretchyCharacters[index].extensionGlyph, gGlyphHeight);
    258                         remaining -= gGlyphHeight;
    259                     }
    260                 }
    261 
    262                 // The bottom glyph in the stack.
    263                 createGlyph(stretchyCharacters[index].bottomGlyph, 0, gBottomGlyphTopAdjust);
    264             }
    265         } else {
    266             // We do not have a middle glyph and so we just extend from the top to the bottom glyph.
    267             int remaining = m_stretchHeight - 2 * gGlyphHeight;
    268             createGlyph(stretchyCharacters[index].topGlyph, gGlyphHeight, gTopGlyphTopAdjust);
    269             while (remaining > 0) {
    270                 if (remaining < gGlyphHeight) {
    271                     createGlyph(stretchyCharacters[index].extensionGlyph, remaining);
    272                     remaining = 0;
    273                 } else {
    274                     createGlyph(stretchyCharacters[index].extensionGlyph, gGlyphHeight);
    275                     remaining -= gGlyphHeight;
    276                 }
    277             }
    278             createGlyph(stretchyCharacters[index].bottomGlyph, 0, gBottomGlyphTopAdjust);
    279         }
    280     }
    281 }
    282 
    283 RefPtr<RenderStyle> RenderMathMLOperator::createStackableStyle(int size, int topRelative)
    284 {
    285     RefPtr<RenderStyle> newStyle = RenderStyle::create();
    286     newStyle->inheritFrom(style());
    287     newStyle->setDisplay(BLOCK);
    288 
    289     FontDescription desc;
    290     desc.setIsAbsoluteSize(true);
    291     desc.setSpecifiedSize(gGlyphFontSize);
    292     desc.setComputedSize(gGlyphFontSize);
    293     newStyle->setFontDescription(desc);
    294     newStyle->font().update(newStyle->font().fontSelector());
    295     newStyle->setLineHeight(Length(gGlyphLineHeight, Fixed));
    296     newStyle->setVerticalAlign(TOP);
    297 
    298     if (size > 0)
    299         newStyle->setMaxHeight(Length(size, Fixed));
    300 
    301     newStyle->setOverflowY(OHIDDEN);
    302     newStyle->setOverflowX(OHIDDEN);
    303     if (topRelative) {
    304         newStyle->setTop(Length(topRelative, Fixed));
    305         newStyle->setPosition(RelativePosition);
    306     }
    307 
    308     return newStyle;
    309 }
    310 
    311 RenderBlock* RenderMathMLOperator::createGlyph(UChar glyph, int size, int charRelative, int topRelative)
    312 {
    313     RenderBlock* container = new (renderArena()) RenderMathMLBlock(node());
    314     container->setStyle(createStackableStyle(size, topRelative).release());
    315     addChild(container);
    316     RenderBlock* parent = container;
    317     if (charRelative) {
    318         RenderBlock* charBlock = new (renderArena()) RenderBlock(node());
    319         RefPtr<RenderStyle> charStyle = RenderStyle::create();
    320         charStyle->inheritFrom(container->style());
    321         charStyle->setDisplay(INLINE_BLOCK);
    322         charStyle->setTop(Length(charRelative, Fixed));
    323         charStyle->setPosition(RelativePosition);
    324         charBlock->setStyle(charStyle);
    325         parent->addChild(charBlock);
    326         parent = charBlock;
    327     }
    328 
    329     RenderText* text = new (renderArena()) RenderText(node(), StringImpl::create(&glyph, 1));
    330     text->setStyle(container->style());
    331     parent->addChild(text);
    332     return container;
    333 }
    334 
    335 int RenderMathMLOperator::baselinePosition(FontBaseline, bool firstLine, LineDirectionMode lineDirection, LinePositionMode linePositionMode) const
    336 {
    337     if (m_isStacked)
    338         return m_stretchHeight * 2 / 3 - (m_stretchHeight - static_cast<int>(m_stretchHeight / gOperatorExpansion)) / 2;
    339     return RenderBlock::baselinePosition(AlphabeticBaseline, firstLine, lineDirection, linePositionMode);
    340 }
    341 
    342 }
    343 
    344 #endif
    345