d035453ab49096a74beeb9446916a99751da32f9
[WebKit-https.git] / Source / WebCore / rendering / mathml / RenderMathMLRoot.cpp
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 "GraphicsContext.h"
34 #include "MathMLNames.h"
35 #include "PaintInfo.h"
36
37 namespace WebCore {
38     
39 using namespace MathMLNames;
40
41 // Left margin of the radical (px)
42 const int gRadicalLeftMargin = 3;
43 // Bottom padding of the radical (px)
44 const int gRadicalBasePad = 3;
45 // Threshold above which the radical shape is modified to look nice with big bases (%)
46 const float gThresholdBaseHeight = 1.5f;
47 // Radical width (%)
48 const float gRadicalWidth = 0.75f;
49 // Horizontal position of the bottom point of the radical (%)
50 const float gRadicalBottomPointXPos= 0.5f;
51 // Horizontal position of the top left point of the radical (%)
52 const float gRadicalTopLeftPointXPos = 0.8f;
53 // Vertical position of the top left point of the radical (%)
54 const float gRadicalTopLeftPointYPos = 0.625f; 
55 // Vertical shift of the left end point of the radical (%)
56 const float gRadicalLeftEndYShift = 0.05f;
57 // Root padding around the base (%)
58 const float gRootPadding = 0.2f;
59 // Additional bottom root padding (%)
60 const float gRootBottomPadding = 0.2f;
61     
62 // Radical line thickness (%)
63 const float gRadicalLineThickness = 0.02f;
64 // Radical thick line thickness (%)
65 const float gRadicalThickLineThickness = 0.1f;
66     
67 RenderMathMLRoot::RenderMathMLRoot(Element* element)
68     : RenderMathMLBlock(element)
69 {
70 }
71
72 void RenderMathMLRoot::addChild(RenderObject* child, RenderObject* )
73 {
74     if (isEmpty()) {
75         // Add a block for the index
76         RenderBlock* indexWrapper = createAlmostAnonymousBlock(INLINE_BLOCK);
77         RenderBlock::addChild(indexWrapper);
78         
79         // FIXME: the wrapping does not seem to be needed anymore.
80         // this is the base, so wrap it so we can pad it
81         RenderBlock* baseWrapper = createAlmostAnonymousBlock(INLINE_BLOCK);
82         baseWrapper->style()->setPaddingLeft(Length(5 * gRadicalWidth, Percent));
83         RenderBlock::addChild(baseWrapper);
84         baseWrapper->addChild(child);
85     } else {
86         // always add to the index
87         firstChild()->addChild(child);
88     }
89 }
90     
91 void RenderMathMLRoot::paint(PaintInfo& info, const LayoutPoint& paintOffset)
92 {
93     RenderMathMLBlock::paint(info, paintOffset);
94     
95     if (info.context->paintingDisabled())
96         return;
97
98     if (!firstChild() || !lastChild())
99         return;
100
101     IntPoint adjustedPaintOffset = roundedIntPoint(paintOffset + location());
102     
103     RenderBoxModelObject* indexBox = toRenderBoxModelObject(lastChild());
104     
105     int maxHeight = indexBox->pixelSnappedOffsetHeight();
106     // default to the font size in pixels if we're empty
107     if (!maxHeight)
108         maxHeight = style()->fontSize();
109     int width = indexBox->pixelSnappedOffsetWidth();
110     
111     int indexWidth = 0;
112     RenderObject* current = firstChild();
113     while (current != lastChild()) {
114         if (current->isBoxModelObject()) {
115             RenderBoxModelObject* box = toRenderBoxModelObject(current);
116             indexWidth += box->pixelSnappedOffsetWidth();
117         }
118         current = current->nextSibling();
119     }
120     
121     int frontWidth = static_cast<int>(style()->fontSize() * gRadicalWidth);
122     int topStartShift = 0;
123     // Base height above which the shape of the root changes
124     int thresholdHeight = static_cast<int>(gThresholdBaseHeight * style()->fontSize());
125     
126     if (maxHeight > thresholdHeight && thresholdHeight) {
127         float shift = (maxHeight - thresholdHeight) / static_cast<float>(thresholdHeight);
128         if (shift > 1.)
129             shift = 1.0f;
130         topStartShift = static_cast<int>(gRadicalBottomPointXPos * frontWidth * shift);
131     }
132     
133     width += topStartShift;
134     
135     int rootPad = static_cast<int>(gRootPadding * style()->fontSize());
136     int start = adjustedPaintOffset.x() + indexWidth + gRadicalLeftMargin + style()->paddingLeft().value() - rootPad;
137     adjustedPaintOffset.setY(adjustedPaintOffset.y() + style()->paddingTop().value() - rootPad);
138     
139     FloatPoint topStart(start - topStartShift, adjustedPaintOffset.y());
140     FloatPoint bottomLeft(start - gRadicalBottomPointXPos * frontWidth , adjustedPaintOffset.y() + maxHeight + gRadicalBasePad);
141     FloatPoint topLeft(start - gRadicalTopLeftPointXPos * frontWidth , adjustedPaintOffset.y() + gRadicalTopLeftPointYPos * maxHeight);
142     FloatPoint leftEnd(start - frontWidth , topLeft.y() + gRadicalLeftEndYShift * style()->fontSize());
143     
144     GraphicsContextStateSaver stateSaver(*info.context);
145     
146     info.context->setStrokeThickness(gRadicalLineThickness * style()->fontSize());
147     info.context->setStrokeStyle(SolidStroke);
148     info.context->setStrokeColor(style()->visitedDependentColor(CSSPropertyColor), ColorSpaceDeviceRGB);
149     info.context->setLineJoin(MiterJoin);
150     info.context->setMiterLimit(style()->fontSize());
151     
152     Path root;
153     
154     root.moveTo(FloatPoint(topStart.x() + width, adjustedPaintOffset.y()));
155     // draw top
156     root.addLineTo(topStart);
157     // draw from top left corner to bottom point of radical
158     root.addLineTo(bottomLeft);
159     // draw from bottom point to top of left part of radical base "pocket"
160     root.addLineTo(topLeft);
161     // draw to end
162     root.addLineTo(leftEnd);
163     
164     info.context->strokePath(root);
165     
166     GraphicsContextStateSaver maskStateSaver(*info.context);
167     
168     // Build a mask to draw the thick part of the root.
169     Path mask;
170     
171     mask.moveTo(topStart);
172     mask.addLineTo(bottomLeft);
173     mask.addLineTo(topLeft);
174     mask.addLineTo(FloatPoint(2 * topLeft.x() - leftEnd.x(), 2 * topLeft.y() - leftEnd.y()));
175     
176     info.context->clip(mask);
177     
178     // Draw the thick part of the root.
179     info.context->setStrokeThickness(gRadicalThickLineThickness * style()->fontSize());
180     info.context->setLineCap(SquareCap);
181     
182     Path line;
183     line.moveTo(bottomLeft);
184     line.addLineTo(topLeft);
185
186     info.context->strokePath(line);
187 }
188
189 void RenderMathMLRoot::layout()
190 {
191     RenderBlock::layout();
192
193     if (!firstChild() || !lastChild())
194         return;
195
196     int maxHeight = toRenderBoxModelObject(lastChild())->pixelSnappedOffsetHeight();
197     
198     RenderObject* current = lastChild()->firstChild();
199     if (current)
200         current->style()->setVerticalAlign(BASELINE);
201     
202     if (!maxHeight)
203         maxHeight = style()->fontSize();
204     
205     // Base height above which the shape of the root changes
206     int thresholdHeight = static_cast<int>(gThresholdBaseHeight * style()->fontSize());
207     int topStartShift = 0;
208     
209     if (maxHeight > thresholdHeight && thresholdHeight) {
210         float shift = (maxHeight - thresholdHeight) / static_cast<float>(thresholdHeight);
211         if (shift > 1.)
212             shift = 1.0f;
213         int frontWidth = static_cast<int>(style()->fontSize() * gRadicalWidth);
214         topStartShift = static_cast<int>(gRadicalBottomPointXPos * frontWidth * shift);
215         
216         style()->setPaddingBottom(Length(static_cast<int>(gRootBottomPadding * style()->fontSize()), Fixed));
217     }
218     
219     // Positioning of the index
220     RenderObject* possibleIndex = firstChild()->firstChild();
221     while (possibleIndex && !possibleIndex->isBoxModelObject())
222         possibleIndex = possibleIndex->nextSibling();
223     RenderBoxModelObject* indexBox = toRenderBoxModelObject(possibleIndex);
224     if (!indexBox)
225         return;
226     
227     int indexShift = indexBox->pixelSnappedOffsetWidth() + topStartShift;
228     int radicalHeight = static_cast<int>((1 - gRadicalTopLeftPointYPos) * maxHeight);
229     int rootMarginTop = radicalHeight + style()->paddingBottom().value() + indexBox->pixelSnappedOffsetHeight()
230         - (maxHeight + static_cast<int>(gRootPadding * style()->fontSize()));
231     
232     style()->setPaddingLeft(Length(indexShift, Fixed));
233     if (rootMarginTop > 0)
234         style()->setPaddingTop(Length(rootMarginTop + static_cast<int>(gRootPadding * style()->fontSize()), Fixed));
235     
236     setNeedsLayout(true);
237     setPreferredLogicalWidthsDirty(true, false);
238     RenderBlock::layout();
239
240     indexBox->style()->setBottom(Length(radicalHeight + style()->paddingBottom().value(), Fixed));
241
242     // Now that we've potentially changed its position, we need layout the index again.
243     indexBox->setNeedsLayout(true);
244     indexBox->layout();
245 }
246     
247 }
248
249 #endif // ENABLE(MATHML)