1/* 2 * Copyright (C) 2009 Alex Milowski (alex@milowski.com). All rights reserved. 3 * Copyright (C) 2010 François Sausset (sausset@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 "RenderMathMLRoot.h" 32 33#include "FontCache.h" 34#include "GraphicsContext.h" 35#include "PaintInfo.h" 36#include "RenderIterator.h" 37#include "RenderMathMLRadicalOperator.h" 38 39namespace WebCore { 40 41// RenderMathMLRoot implements drawing of radicals via the <mroot> and <msqrt> elements. For valid MathML elements, the DOM is 42// 43// <mroot> Base Index </mroot> 44// <msqrt> Child1 Child2 ... ChildN </msqrt> 45// 46// and the structure of the render tree will be 47// 48// IndexWrapper RadicalWrapper BaseWrapper 49// 50// where RadicalWrapper contains an <mo>√</mo>. 51// For <mroot>, the IndexWrapper and BaseWrapper should contain exactly one child (Index and Base respectively). 52// For <msqrt>, the IndexWrapper should be empty and the BaseWrapper can contain any number of children (Child1, ... ChildN). 53// 54// In order to accept invalid markup and to handle <mroot> and <msqrt> consistently, we will allow any number of children in the BaseWrapper of <mroot> too. 55// We will allow the IndexWrapper to be empty and it will always contain the last child of the <mroot> if there are at least 2 elements. 56 57RenderMathMLRoot::RenderMathMLRoot(Element& element, PassRef<RenderStyle> style) 58 : RenderMathMLBlock(element, WTF::move(style)) 59{ 60} 61 62RenderMathMLRoot::RenderMathMLRoot(Document& document, PassRef<RenderStyle> style) 63 : RenderMathMLBlock(document, WTF::move(style)) 64{ 65} 66 67RenderMathMLRootWrapper* RenderMathMLRoot::baseWrapper() const 68{ 69 ASSERT(!isEmpty()); 70 return toRenderMathMLRootWrapper(lastChild()); 71} 72 73RenderMathMLBlock* RenderMathMLRoot::radicalWrapper() const 74{ 75 ASSERT(!isEmpty()); 76 return toRenderMathMLBlock(lastChild()->previousSibling()); 77} 78 79RenderMathMLRootWrapper* RenderMathMLRoot::indexWrapper() const 80{ 81 ASSERT(!isEmpty()); 82 return isRenderMathMLSquareRoot() ? nullptr : toRenderMathMLRootWrapper(firstChild()); 83} 84 85RenderMathMLRadicalOperator* RenderMathMLRoot::radicalOperator() const 86{ 87 ASSERT(!isEmpty()); 88 return toRenderMathMLRadicalOperator(radicalWrapper()->firstChild()); 89} 90 91void RenderMathMLRoot::restructureWrappers() 92{ 93 ASSERT(!isEmpty()); 94 95 auto base = baseWrapper(); 96 auto index = indexWrapper(); 97 auto radical = radicalWrapper(); 98 99 // For visual consistency with the initial state, we remove the radical when the base/index wrappers become empty. 100 if (base->isEmpty() && (!index || index->isEmpty())) { 101 if (!radical->isEmpty()) { 102 auto child = radicalOperator(); 103 radical->removeChild(*child); 104 child->destroy(); 105 } 106 // FIXME: early return!!! 107 } 108 109 if (radical->isEmpty()) { 110 // We create the radical operator. 111 RenderPtr<RenderMathMLRadicalOperator> radicalOperator = createRenderer<RenderMathMLRadicalOperator>(document(), RenderStyle::createAnonymousStyleWithDisplay(&style(), FLEX)); 112 radicalOperator->initializeStyle(); 113 radical->addChild(radicalOperator.leakPtr()); 114 } 115 116 if (isRenderMathMLSquareRoot()) 117 return; 118 119 if (auto childToMove = base->lastChild()) { 120 // We move the last child of the base wrapper into the index wrapper if the index wrapper is empty and the base wrapper has at least two children. 121 if (childToMove->previousSibling() && index->isEmpty()) { 122 base->removeChildWithoutRestructuring(*childToMove); 123 index->addChild(childToMove); 124 } 125 } 126 127 if (auto childToMove = index->firstChild()) { 128 // We move the first child of the index wrapper into the base wrapper if: 129 // - either the index wrapper has at least two children. 130 // - or the base wrapper is empty but the index wrapper is not. 131 if (childToMove->nextSibling() || base->isEmpty()) { 132 index->removeChildWithoutRestructuring(*childToMove); 133 base->addChild(childToMove); 134 } 135 } 136} 137 138void RenderMathMLRoot::addChild(RenderObject* newChild, RenderObject* beforeChild) 139{ 140 if (isEmpty()) { 141 if (!isRenderMathMLSquareRoot()) { 142 // We add the IndexWrapper. 143 RenderMathMLBlock::addChild(RenderMathMLRootWrapper::createAnonymousWrapper(this).leakPtr()); 144 } 145 146 // We create the radicalWrapper 147 RenderMathMLBlock::addChild(RenderMathMLBlock::createAnonymousMathMLBlock().leakPtr()); 148 149 // We create the BaseWrapper. 150 RenderMathMLBlock::addChild(RenderMathMLRootWrapper::createAnonymousWrapper(this).leakPtr()); 151 152 updateStyle(); 153 } 154 155 // We insert the child. 156 auto base = baseWrapper(); 157 auto index = indexWrapper(); 158 RenderElement* actualParent; 159 RenderElement* actualBeforeChild; 160 if (isRenderMathMLSquareRoot()) { 161 // For square root, we always insert the child into the base wrapper. 162 actualParent = base; 163 if (beforeChild && beforeChild->parent() == base) 164 actualBeforeChild = toRenderElement(beforeChild); 165 else 166 actualBeforeChild = nullptr; 167 } else { 168 // For mroot, we insert the child into the parent of beforeChild, or at the end of the index. The wrapper structure is reorganize below. 169 actualParent = beforeChild ? beforeChild->parent() : nullptr; 170 if (actualParent == base || actualParent == index) 171 actualBeforeChild = toRenderElement(beforeChild); 172 else { 173 actualParent = index; 174 actualBeforeChild = nullptr; 175 } 176 } 177 actualParent->addChild(newChild, actualBeforeChild); 178 restructureWrappers(); 179} 180 181void RenderMathMLRoot::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) 182{ 183 RenderMathMLBlock::styleDidChange(diff, oldStyle); 184 if (!isEmpty()) 185 updateStyle(); 186} 187 188void RenderMathMLRoot::updateFromElement() 189{ 190 RenderMathMLBlock::updateFromElement(); 191 if (!isEmpty()) 192 updateStyle(); 193} 194 195void RenderMathMLRoot::updateStyle() 196{ 197 ASSERT(!isEmpty()); 198 199 // We set some constants to draw the radical, as defined in the OpenType MATH tables. 200 201 m_ruleThickness = 0.05f * style().font().size(); 202 203 // FIXME: The recommended default for m_verticalGap in displaystyle is rule thickness + 1/4 x-height (https://bugs.webkit.org/show_bug.cgi?id=118737). 204 m_verticalGap = 11 * m_ruleThickness / 4; 205 m_extraAscender = m_ruleThickness; 206 LayoutUnit kernBeforeDegree = 5 * style().font().size() / 18; 207 LayoutUnit kernAfterDegree = -10 * style().font().size() / 18; 208 m_degreeBottomRaisePercent = 0.6f; 209 210 const auto& primaryFontData = style().font().primaryFont(); 211 if (primaryFontData && primaryFontData->mathData()) { 212 // FIXME: m_verticalGap should use RadicalDisplayStyleVertical in display mode (https://bugs.webkit.org/show_bug.cgi?id=118737). 213 m_verticalGap = primaryFontData->mathData()->getMathConstant(primaryFontData, OpenTypeMathData::RadicalVerticalGap); 214 m_ruleThickness = primaryFontData->mathData()->getMathConstant(primaryFontData, OpenTypeMathData::RadicalRuleThickness); 215 m_extraAscender = primaryFontData->mathData()->getMathConstant(primaryFontData, OpenTypeMathData::RadicalExtraAscender); 216 217 if (!isRenderMathMLSquareRoot()) { 218 kernBeforeDegree = primaryFontData->mathData()->getMathConstant(primaryFontData, OpenTypeMathData::RadicalKernBeforeDegree); 219 kernAfterDegree = primaryFontData->mathData()->getMathConstant(primaryFontData, OpenTypeMathData::RadicalKernAfterDegree); 220 m_degreeBottomRaisePercent = primaryFontData->mathData()->getMathConstant(primaryFontData, OpenTypeMathData::RadicalDegreeBottomRaisePercent); 221 } 222 } 223 224 // We set the style of the anonymous wrappers. 225 226 auto radical = radicalWrapper(); 227 auto radicalStyle = RenderStyle::createAnonymousStyleWithDisplay(&style(), FLEX); 228 radicalStyle.get().setMarginTop(Length(0, Fixed)); // This will be updated in RenderMathMLRoot::layout(). 229 radical->setStyle(WTF::move(radicalStyle)); 230 radical->setNeedsLayoutAndPrefWidthsRecalc(); 231 232 auto base = baseWrapper(); 233 auto baseStyle = RenderStyle::createAnonymousStyleWithDisplay(&style(), FLEX); 234 baseStyle.get().setMarginTop(Length(0, Fixed)); // This will be updated in RenderMathMLRoot::layout(). 235 baseStyle.get().setAlignItems(AlignBaseline); 236 base->setStyle(WTF::move(baseStyle)); 237 base->setNeedsLayoutAndPrefWidthsRecalc(); 238 239 if (!isRenderMathMLSquareRoot()) { 240 // For mroot, we also set the style of the index wrapper. 241 auto index = indexWrapper(); 242 auto indexStyle = RenderStyle::createAnonymousStyleWithDisplay(&style(), FLEX); 243 indexStyle.get().setMarginTop(Length(0, Fixed)); // This will be updated in RenderMathMLRoot::layout(). 244 indexStyle.get().setMarginStart(Length(kernBeforeDegree, Fixed)); 245 indexStyle.get().setMarginEnd(Length(kernAfterDegree, Fixed)); 246 indexStyle.get().setAlignItems(AlignBaseline); 247 index->setStyle(WTF::move(indexStyle)); 248 index->setNeedsLayoutAndPrefWidthsRecalc(); 249 } 250} 251 252int RenderMathMLRoot::firstLineBaseline() const 253{ 254 if (!isEmpty()) { 255 auto base = baseWrapper(); 256 return static_cast<int>(lroundf(base->firstLineBaseline() + base->marginTop())); 257 } 258 259 return RenderMathMLBlock::firstLineBaseline(); 260} 261 262void RenderMathMLRoot::layout() 263{ 264 if (isEmpty()) { 265 RenderMathMLBlock::layout(); 266 return; 267 } 268 269 // FIXME: It seems that changing the top margin of the base below modifies its logical height and leads to reftest failures. 270 // For now, we workaround that by avoiding to recompute the child margins if they were not reset in updateStyle(). 271 auto base = baseWrapper(); 272 if (base->marginTop() > 0) { 273 RenderMathMLBlock::layout(); 274 return; 275 } 276 277 // We layout the children. 278 for (RenderObject* child = firstChild(); child; child = child->nextSibling()) { 279 if (child->needsLayout()) 280 toRenderElement(child)->layout(); 281 } 282 283 auto radical = radicalOperator(); 284 if (radical) { 285 // We stretch the radical sign to cover the height of the base wrapper. 286 float baseHeight = base->logicalHeight(); 287 float baseHeightAboveBaseline = base->firstLineBaseline(); 288 if (baseHeightAboveBaseline == -1) 289 baseHeightAboveBaseline = baseHeight; 290 float baseDepthBelowBaseline = baseHeight - baseHeightAboveBaseline; 291 baseHeightAboveBaseline += m_verticalGap; 292 radical->stretchTo(baseHeightAboveBaseline, baseDepthBelowBaseline); 293 294 // We modify the top margins to adjust the vertical positions of wrappers. 295 float radicalTopMargin = m_extraAscender; 296 float baseTopMargin = m_verticalGap + m_ruleThickness + m_extraAscender; 297 if (!isRenderMathMLSquareRoot()) { 298 // For mroot, we try to place the index so the space below its baseline is m_degreeBottomRaisePercent times the height of the radical. 299 auto index = indexWrapper(); 300 float indexHeight = 0; 301 if (!index->isEmpty()) 302 indexHeight = toRenderBlock(index->firstChild())->logicalHeight(); 303 float indexTopMargin = (1.0 - m_degreeBottomRaisePercent) * radical->stretchSize() + radicalTopMargin - indexHeight; 304 if (indexTopMargin < 0) { 305 // If the index is too tall, we must add space at the top of renderer. 306 radicalTopMargin -= indexTopMargin; 307 baseTopMargin -= indexTopMargin; 308 indexTopMargin = 0; 309 } 310 index->style().setMarginTop(Length(indexTopMargin, Fixed)); 311 } 312 radical->style().setMarginTop(Length(radicalTopMargin, Fixed)); 313 base->style().setMarginTop(Length(baseTopMargin, Fixed)); 314 } 315 316 RenderMathMLBlock::layout(); 317} 318 319void RenderMathMLRoot::paint(PaintInfo& info, const LayoutPoint& paintOffset) 320{ 321 RenderMathMLBlock::paint(info, paintOffset); 322 323 if (isEmpty() || info.context->paintingDisabled() || style().visibility() != VISIBLE) 324 return; 325 326 auto base = baseWrapper(); 327 auto radical = radicalOperator(); 328 if (!base || !radical || !m_ruleThickness) 329 return; 330 331 // We draw the radical line. 332 GraphicsContextStateSaver stateSaver(*info.context); 333 334 info.context->setStrokeThickness(m_ruleThickness); 335 info.context->setStrokeStyle(SolidStroke); 336 info.context->setStrokeColor(style().visitedDependentColor(CSSPropertyColor), ColorSpaceDeviceRGB); 337 338 // The preferred width of the radical is sometimes incorrect, so we draw a slightly longer line to ensure it touches the radical symbol (https://bugs.webkit.org/show_bug.cgi?id=130326). 339 LayoutUnit sizeError = radical->trailingSpaceError(); 340 IntPoint adjustedPaintOffset = roundedIntPoint(paintOffset + location() + base->location() + LayoutPoint(-sizeError, -(m_verticalGap + m_ruleThickness / 2))); 341 info.context->drawLine(adjustedPaintOffset, IntPoint(adjustedPaintOffset.x() + base->pixelSnappedOffsetWidth() + sizeError, adjustedPaintOffset.y())); 342} 343 344RenderPtr<RenderMathMLRootWrapper> RenderMathMLRootWrapper::createAnonymousWrapper(RenderMathMLRoot* renderObject) 345{ 346 RenderPtr<RenderMathMLRootWrapper> newBlock = createRenderer<RenderMathMLRootWrapper>(renderObject->document(), RenderStyle::createAnonymousStyleWithDisplay(&renderObject->style(), FLEX)); 347 newBlock->initializeStyle(); 348 return newBlock; 349} 350 351RenderObject* RenderMathMLRootWrapper::removeChildWithoutRestructuring(RenderObject& child) 352{ 353 return RenderMathMLBlock::removeChild(child); 354} 355 356RenderObject* RenderMathMLRootWrapper::removeChild(RenderObject& child) 357{ 358 RenderObject* next = RenderMathMLBlock::removeChild(child); 359 360 if (!(beingDestroyed() || documentBeingDestroyed())) 361 toRenderMathMLRoot(parent())->restructureWrappers(); 362 363 return next; 364} 365 366} 367 368#endif // ENABLE(MATHML) 369