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