1 /*
2 * Copyright (C) 2010 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 "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
RenderMathMLOperator(Node * container)41 RenderMathMLOperator::RenderMathMLOperator(Node* container)
42 : RenderMathMLBlock(container)
43 , m_stretchHeight(0)
44 , m_operator(0)
45 {
46 }
47
RenderMathMLOperator(Node * container,UChar operatorChar)48 RenderMathMLOperator::RenderMathMLOperator(Node* container, UChar operatorChar)
49 : RenderMathMLBlock(container)
50 , m_stretchHeight(0)
51 , m_operator(convertHyphenMinusToMinusSign(operatorChar))
52 {
53 }
54
isChildAllowed(RenderObject *,RenderStyle *) const55 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
stretchToHeight(int height)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
layout()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
updateFromElement()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
createStackableStyle(int size,int topRelative)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
createGlyph(UChar glyph,int size,int charRelative,int topRelative)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
baselinePosition(FontBaseline,bool firstLine,LineDirectionMode lineDirection,LinePositionMode linePositionMode) const335 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