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