1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsMathMLmfencedFrame.h"
8 #include "nsRenderingContext.h"
9 #include "nsMathMLChar.h"
12 using namespace mozilla
;
15 // <mfenced> -- surround content with a pair of fences
19 NS_NewMathMLmfencedFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
)
21 return new (aPresShell
) nsMathMLmfencedFrame(aContext
);
24 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmfencedFrame
)
26 nsMathMLmfencedFrame::~nsMathMLmfencedFrame()
28 RemoveFencesAndSeparators();
32 nsMathMLmfencedFrame::InheritAutomaticData(nsIFrame
* aParent
)
34 // let the base class get the default from our parent
35 nsMathMLContainerFrame::InheritAutomaticData(aParent
);
37 mPresentationData
.flags
|= NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY
;
39 RemoveFencesAndSeparators();
40 CreateFencesAndSeparators(PresContext());
46 nsMathMLmfencedFrame::SetInitialChildList(ChildListID aListID
,
47 nsFrameList
& aChildList
)
49 // First, let the base class do its work
50 nsMathMLContainerFrame::SetInitialChildList(aListID
, aChildList
);
52 // InheritAutomaticData will not get called if our parent is not a mathml
53 // frame, so initialize NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY for
54 // GetPreferredStretchSize() from Reflow().
55 mPresentationData
.flags
|= NS_MATHML_STRETCH_ALL_CHILDREN_VERTICALLY
;
56 // No need to track the style contexts given to our MathML chars.
57 // The Style System will use Get/SetAdditionalStyleContext() to keep them
58 // up-to-date if dynamic changes arise.
59 CreateFencesAndSeparators(PresContext());
63 nsMathMLmfencedFrame::AttributeChanged(int32_t aNameSpaceID
,
67 RemoveFencesAndSeparators();
68 CreateFencesAndSeparators(PresContext());
70 return nsMathMLContainerFrame::
71 AttributeChanged(aNameSpaceID
, aAttribute
, aModType
);
75 nsMathMLmfencedFrame::ChildListChanged(int32_t aModType
)
77 RemoveFencesAndSeparators();
78 CreateFencesAndSeparators(PresContext());
80 return nsMathMLContainerFrame::ChildListChanged(aModType
);
84 nsMathMLmfencedFrame::RemoveFencesAndSeparators()
88 if (mSeparatorsChar
) delete[] mSeparatorsChar
;
92 mSeparatorsChar
= nullptr;
97 nsMathMLmfencedFrame::CreateFencesAndSeparators(nsPresContext
* aPresContext
)
102 // see if the opening fence is there ...
103 if (!mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::open
, value
)) {
104 value
= char16_t('('); // default as per the MathML REC
106 value
.CompressWhitespace();
109 if (!value
.IsEmpty()) {
110 mOpenChar
= new nsMathMLChar
;
111 mOpenChar
->SetData(aPresContext
, value
);
112 ResolveMathMLCharStyle(aPresContext
, mContent
, mStyleContext
, mOpenChar
);
116 // see if the closing fence is there ...
117 if(!mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::close
, value
)) {
118 value
= char16_t(')'); // default as per the MathML REC
120 value
.CompressWhitespace();
123 if (!value
.IsEmpty()) {
124 mCloseChar
= new nsMathMLChar
;
125 mCloseChar
->SetData(aPresContext
, value
);
126 ResolveMathMLCharStyle(aPresContext
, mContent
, mStyleContext
, mCloseChar
);
130 // see if separators are there ...
131 if (!mContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::separators_
, value
)) {
132 value
= char16_t(','); // default as per the MathML REC
134 value
.StripWhitespace();
137 mSeparatorsCount
= value
.Length();
138 if (0 < mSeparatorsCount
) {
139 int32_t sepCount
= mFrames
.GetLength() - 1;
141 mSeparatorsChar
= new nsMathMLChar
[sepCount
];
142 nsAutoString sepChar
;
143 for (int32_t i
= 0; i
< sepCount
; i
++) {
144 if (i
< mSeparatorsCount
) {
148 sepChar
= value
[mSeparatorsCount
-1];
150 mSeparatorsChar
[i
].SetData(aPresContext
, sepChar
);
151 ResolveMathMLCharStyle(aPresContext
, mContent
, mStyleContext
, &mSeparatorsChar
[i
]);
153 mSeparatorsCount
= sepCount
;
155 // No separators. Note that sepCount can be -1 here, so don't
156 // set mSeparatorsCount to it.
157 mSeparatorsCount
= 0;
163 nsMathMLmfencedFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
164 const nsRect
& aDirtyRect
,
165 const nsDisplayListSet
& aLists
)
168 // display the content
169 nsMathMLContainerFrame::BuildDisplayList(aBuilder
, aDirtyRect
, aLists
);
172 // display fences and separators
175 mOpenChar
->Display(aBuilder
, this, aLists
, count
++);
179 mCloseChar
->Display(aBuilder
, this, aLists
, count
++);
182 for (int32_t i
= 0; i
< mSeparatorsCount
; i
++) {
183 mSeparatorsChar
[i
].Display(aBuilder
, this, aLists
, count
++);
188 nsMathMLmfencedFrame::Reflow(nsPresContext
* aPresContext
,
189 nsHTMLReflowMetrics
& aDesiredSize
,
190 const nsHTMLReflowState
& aReflowState
,
191 nsReflowStatus
& aStatus
)
193 aDesiredSize
.ClearSize();
194 aDesiredSize
.SetBlockStartAscent(0);
195 aDesiredSize
.mBoundingMetrics
= nsBoundingMetrics();
198 const nsStyleFont
* font
= StyleFont();
199 nsRefPtr
<nsFontMetrics
> fm
;
200 nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm
));
201 aReflowState
.rendContext
->SetFont(fm
);
202 nscoord axisHeight
, em
;
203 GetAxisHeight(*aReflowState
.rendContext
, fm
, axisHeight
);
205 // leading to be left at the top and the bottom of stretched chars
206 nscoord leading
= NSToCoordRound(0.2f
* em
);
210 // Asking each child to cache its bounding metrics
212 // Note that we don't use the base method nsMathMLContainerFrame::Reflow()
213 // because we want to stretch our fences, separators and stretchy frames using
214 // the *same* initial aDesiredSize.mBoundingMetrics. If we were to use the base
215 // method here, our stretchy frames will be stretched and placed, and we may
216 // end up stretching our fences/separators with a different aDesiredSize.
217 // XXX The above decision was revisited in bug 121748 and this code can be
218 // refactored to use nsMathMLContainerFrame::Reflow() at some stage.
220 nsReflowStatus childStatus
;
221 nsIFrame
* firstChild
= GetFirstPrincipalChild();
222 nsIFrame
* childFrame
= firstChild
;
223 nscoord ascent
= 0, descent
= 0;
224 if (firstChild
|| mOpenChar
|| mCloseChar
|| mSeparatorsCount
> 0) {
225 // We use the ASCII metrics to get our minimum height. This way,
226 // if we have borders or a background, they will fit better with
227 // other elements on the line.
228 ascent
= fm
->MaxAscent();
229 descent
= fm
->MaxDescent();
232 nsHTMLReflowMetrics
childDesiredSize(aReflowState
,
234 | NS_REFLOW_CALC_BOUNDING_METRICS
);
235 WritingMode wm
= childFrame
->GetWritingMode();
236 LogicalSize availSize
= aReflowState
.ComputedSize(wm
);
237 availSize
.BSize(wm
) = NS_UNCONSTRAINEDSIZE
;
238 nsHTMLReflowState
childReflowState(aPresContext
, aReflowState
,
239 childFrame
, availSize
);
240 ReflowChild(childFrame
, aPresContext
, childDesiredSize
,
241 childReflowState
, childStatus
);
242 //NS_ASSERTION(NS_FRAME_IS_COMPLETE(childStatus), "bad status");
243 SaveReflowAndBoundingMetricsFor(childFrame
, childDesiredSize
,
244 childDesiredSize
.mBoundingMetrics
);
246 mozilla::WritingMode outerWM
= aReflowState
.GetWritingMode();
247 nscoord childDescent
= childDesiredSize
.BSize(outerWM
) -
248 childDesiredSize
.BlockStartAscent();
249 if (descent
< childDescent
)
250 descent
= childDescent
;
251 if (ascent
< childDesiredSize
.BlockStartAscent())
252 ascent
= childDesiredSize
.BlockStartAscent();
254 childFrame
= childFrame
->GetNextSibling();
258 // Ask stretchy children to stretch themselves
260 nsBoundingMetrics containerSize
;
261 nsStretchDirection stretchDir
= NS_STRETCH_DIRECTION_VERTICAL
;
263 GetPreferredStretchSize(*aReflowState
.rendContext
,
264 0, /* i.e., without embellishments */
265 stretchDir
, containerSize
);
266 childFrame
= firstChild
;
268 nsIMathMLFrame
* mathmlChild
= do_QueryFrame(childFrame
);
270 nsHTMLReflowMetrics
childDesiredSize(aReflowState
);
271 // retrieve the metrics that was stored at the previous pass
272 GetReflowAndBoundingMetricsFor(childFrame
, childDesiredSize
,
273 childDesiredSize
.mBoundingMetrics
);
275 mathmlChild
->Stretch(*aReflowState
.rendContext
,
276 stretchDir
, containerSize
, childDesiredSize
);
277 // store the updated metrics
278 SaveReflowAndBoundingMetricsFor(childFrame
, childDesiredSize
,
279 childDesiredSize
.mBoundingMetrics
);
281 nscoord childDescent
= childDesiredSize
.Height() - childDesiredSize
.BlockStartAscent();
282 if (descent
< childDescent
)
283 descent
= childDescent
;
284 if (ascent
< childDesiredSize
.BlockStartAscent())
285 ascent
= childDesiredSize
.BlockStartAscent();
287 childFrame
= childFrame
->GetNextSibling();
290 // bug 121748: for surrounding fences & separators, use a size that covers everything
291 GetPreferredStretchSize(*aReflowState
.rendContext
,
292 STRETCH_CONSIDER_EMBELLISHMENTS
,
293 stretchDir
, containerSize
);
295 //////////////////////////////////////////
296 // Prepare the opening fence, separators, and closing fence, and
297 // adjust the origin of children.
299 // we need to center around the axis
300 nscoord delta
= std::max(containerSize
.ascent
- axisHeight
,
301 containerSize
.descent
+ axisHeight
);
302 containerSize
.ascent
= delta
+ axisHeight
;
303 containerSize
.descent
= delta
- axisHeight
;
305 bool isRTL
= StyleVisibility()->mDirection
;
309 ReflowChar(aPresContext
, *aReflowState
.rendContext
, mOpenChar
,
310 NS_MATHML_OPERATOR_FORM_PREFIX
, font
->mScriptLevel
,
311 axisHeight
, leading
, em
, containerSize
, ascent
, descent
, isRTL
);
314 for (i
= 0; i
< mSeparatorsCount
; i
++) {
315 ReflowChar(aPresContext
, *aReflowState
.rendContext
, &mSeparatorsChar
[i
],
316 NS_MATHML_OPERATOR_FORM_INFIX
, font
->mScriptLevel
,
317 axisHeight
, leading
, em
, containerSize
, ascent
, descent
, isRTL
);
321 ReflowChar(aPresContext
, *aReflowState
.rendContext
, mCloseChar
,
322 NS_MATHML_OPERATOR_FORM_POSTFIX
, font
->mScriptLevel
,
323 axisHeight
, leading
, em
, containerSize
, ascent
, descent
, isRTL
);
326 // Adjust the origins of each child.
327 // and update our bounding metrics
331 nsBoundingMetrics bm
;
332 bool firstTime
= true;
333 nsMathMLChar
*leftChar
, *rightChar
;
335 leftChar
= mCloseChar
;
336 rightChar
= mOpenChar
;
338 leftChar
= mOpenChar
;
339 rightChar
= mCloseChar
;
343 PlaceChar(leftChar
, ascent
, bm
, dx
);
344 aDesiredSize
.mBoundingMetrics
= bm
;
349 childFrame
= this->GetLastChild(nsIFrame::kPrincipalList
);
351 childFrame
= firstChild
;
354 nsHTMLReflowMetrics
childSize(aReflowState
);
355 GetReflowAndBoundingMetricsFor(childFrame
, childSize
, bm
);
358 aDesiredSize
.mBoundingMetrics
= bm
;
361 aDesiredSize
.mBoundingMetrics
+= bm
;
363 FinishReflowChild(childFrame
, aPresContext
, childSize
, nullptr,
364 dx
, ascent
- childSize
.BlockStartAscent(), 0);
365 dx
+= childSize
.Width();
367 if (i
< mSeparatorsCount
) {
368 PlaceChar(&mSeparatorsChar
[isRTL
? mSeparatorsCount
- 1 - i
: i
],
370 aDesiredSize
.mBoundingMetrics
+= bm
;
375 childFrame
= childFrame
->GetPrevSibling();
377 childFrame
= childFrame
->GetNextSibling();
382 PlaceChar(rightChar
, ascent
, bm
, dx
);
384 aDesiredSize
.mBoundingMetrics
= bm
;
386 aDesiredSize
.mBoundingMetrics
+= bm
;
389 aDesiredSize
.Width() = aDesiredSize
.mBoundingMetrics
.width
;
390 aDesiredSize
.Height() = ascent
+ descent
;
391 aDesiredSize
.SetBlockStartAscent(ascent
);
393 SetBoundingMetrics(aDesiredSize
.mBoundingMetrics
);
394 SetReference(nsPoint(0, aDesiredSize
.BlockStartAscent()));
396 // see if we should fix the spacing
397 FixInterFrameSpacing(aDesiredSize
);
399 // Finished with these:
400 ClearSavedChildMetrics();
402 // Set our overflow area
403 GatherAndStoreOverflow(&aDesiredSize
);
405 aStatus
= NS_FRAME_COMPLETE
;
406 NS_FRAME_SET_TRUNCATION(aStatus
, aReflowState
, aDesiredSize
);
410 GetCharSpacing(nsMathMLChar
* aMathMLChar
,
411 nsOperatorFlags aForm
,
412 int32_t aScriptLevel
,
415 nscoord
& aRightSpace
)
418 aMathMLChar
->GetData(data
);
419 nsOperatorFlags flags
= 0;
422 bool found
= nsMathMLOperators::LookupOperator(data
, aForm
,
423 &flags
, &lspace
, &rspace
);
425 // We don't want extra space when we are a script
426 if (found
&& aScriptLevel
> 0) {
431 aLeftSpace
= NSToCoordRound(lspace
* em
);
432 aRightSpace
= NSToCoordRound(rspace
* em
);
435 // helper functions to perform the common task of formatting our chars
437 nsMathMLmfencedFrame::ReflowChar(nsPresContext
* aPresContext
,
438 nsRenderingContext
& aRenderingContext
,
439 nsMathMLChar
* aMathMLChar
,
440 nsOperatorFlags aForm
,
441 int32_t aScriptLevel
,
445 nsBoundingMetrics
& aContainerSize
,
450 if (aMathMLChar
&& 0 < aMathMLChar
->Length()) {
453 GetCharSpacing(aMathMLChar
, aForm
, aScriptLevel
, em
, leftSpace
, rightSpace
);
455 // stretch the char to the appropriate height if it is not big enough.
456 nsBoundingMetrics charSize
;
457 nsresult res
= aMathMLChar
->Stretch(aPresContext
, aRenderingContext
,
458 NS_STRETCH_DIRECTION_VERTICAL
,
459 aContainerSize
, charSize
,
460 NS_STRETCH_NORMAL
, aRTL
);
462 if (NS_STRETCH_DIRECTION_UNSUPPORTED
!= aMathMLChar
->GetStretchDirection()) {
463 // has changed... so center the char around the axis
464 nscoord height
= charSize
.ascent
+ charSize
.descent
;
465 charSize
.ascent
= height
/2 + axisHeight
;
466 charSize
.descent
= height
- charSize
.ascent
;
469 // either it hasn't changed or stretching the char failed (i.e.,
470 // GetBoundingMetrics failed)
472 if (NS_FAILED(res
)) {
474 aMathMLChar
->GetData(data
);
475 nsBoundingMetrics metrics
=
476 aRenderingContext
.GetBoundingMetrics(data
.get(), data
.Length());
477 charSize
.ascent
= metrics
.ascent
;
478 charSize
.descent
= metrics
.descent
;
479 charSize
.width
= metrics
.width
;
480 // Set this as the bounding metrics of the MathMLChar to leave
481 // the necessary room to paint the char.
482 aMathMLChar
->SetBoundingMetrics(charSize
);
486 if (aAscent
< charSize
.ascent
+ leading
)
487 aAscent
= charSize
.ascent
+ leading
;
488 if (aDescent
< charSize
.descent
+ leading
)
489 aDescent
= charSize
.descent
+ leading
;
491 // account the spacing
492 charSize
.width
+= leftSpace
+ rightSpace
;
494 // x-origin is used to store lspace ...
495 // y-origin is used to stored the ascent ...
496 aMathMLChar
->SetRect(nsRect(leftSpace
,
497 charSize
.ascent
, charSize
.width
,
498 charSize
.ascent
+ charSize
.descent
));
504 nsMathMLmfencedFrame::PlaceChar(nsMathMLChar
* aMathMLChar
,
505 nscoord aDesiredAscent
,
506 nsBoundingMetrics
& bm
,
509 aMathMLChar
->GetBoundingMetrics(bm
);
511 // the char's x-origin was used to store lspace ...
512 // the char's y-origin was used to store the ascent ...
513 // the char's width was used to store the advance with (with spacing) ...
515 aMathMLChar
->GetRect(rect
);
517 nscoord dy
= aDesiredAscent
- rect
.y
;
518 if (aMathMLChar
->GetStretchDirection() != NS_STRETCH_DIRECTION_UNSUPPORTED
) {
519 // the stretchy char will be centered around the axis
520 // so we adjust the returned bounding metrics accordingly
521 bm
.descent
= (bm
.ascent
+ bm
.descent
) - rect
.y
;
525 aMathMLChar
->SetRect(nsRect(dx
+ rect
.x
, dy
, bm
.width
, rect
.height
));
527 bm
.leftBearing
+= rect
.x
;
528 bm
.rightBearing
+= rect
.x
;
530 // return rect.width since it includes lspace and rspace
531 bm
.width
= rect
.width
;
536 GetMaxCharWidth(nsPresContext
* aPresContext
,
537 nsRenderingContext
* aRenderingContext
,
538 nsMathMLChar
* aMathMLChar
,
539 nsOperatorFlags aForm
,
540 int32_t aScriptLevel
,
543 nscoord width
= aMathMLChar
->GetMaxWidth(aPresContext
, *aRenderingContext
);
545 if (0 < aMathMLChar
->Length()) {
548 GetCharSpacing(aMathMLChar
, aForm
, aScriptLevel
, em
, leftSpace
, rightSpace
);
550 width
+= leftSpace
+ rightSpace
;
557 nsMathMLmfencedFrame::GetIntrinsicISizeMetrics(nsRenderingContext
* aRenderingContext
, nsHTMLReflowMetrics
& aDesiredSize
)
561 nsPresContext
* presContext
= PresContext();
562 const nsStyleFont
* font
= StyleFont();
563 nsRefPtr
<nsFontMetrics
> fm
;
564 nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm
));
570 GetMaxCharWidth(presContext
, aRenderingContext
, mOpenChar
,
571 NS_MATHML_OPERATOR_FORM_PREFIX
, font
->mScriptLevel
, em
);
575 nsIFrame
* childFrame
= GetFirstPrincipalChild();
577 // XXX This includes margin while Reflow currently doesn't consider
578 // margin, so we may end up with too much space, but, with stretchy
579 // characters, this is an approximation anyway.
580 width
+= nsLayoutUtils::IntrinsicForContainer(aRenderingContext
, childFrame
,
581 nsLayoutUtils::PREF_ISIZE
);
583 if (i
< mSeparatorsCount
) {
585 GetMaxCharWidth(presContext
, aRenderingContext
, &mSeparatorsChar
[i
],
586 NS_MATHML_OPERATOR_FORM_INFIX
, font
->mScriptLevel
, em
);
590 childFrame
= childFrame
->GetNextSibling();
595 GetMaxCharWidth(presContext
, aRenderingContext
, mCloseChar
,
596 NS_MATHML_OPERATOR_FORM_POSTFIX
, font
->mScriptLevel
, em
);
599 aDesiredSize
.Width() = width
;
600 aDesiredSize
.mBoundingMetrics
.width
= width
;
601 aDesiredSize
.mBoundingMetrics
.leftBearing
= 0;
602 aDesiredSize
.mBoundingMetrics
.rightBearing
= width
;
606 nsMathMLmfencedFrame::FixInterFrameSpacing(nsHTMLReflowMetrics
& aDesiredSize
)
608 nscoord gap
= nsMathMLContainerFrame::FixInterFrameSpacing(aDesiredSize
);
613 mOpenChar
->GetRect(rect
);
615 mOpenChar
->SetRect(rect
);
618 mCloseChar
->GetRect(rect
);
620 mCloseChar
->SetRect(rect
);
622 for (int32_t i
= 0; i
< mSeparatorsCount
; i
++) {
623 mSeparatorsChar
[i
].GetRect(rect
);
625 mSeparatorsChar
[i
].SetRect(rect
);
630 // ----------------------
631 // the Style System will use these to pass the proper style context to our MathMLChar
633 nsMathMLmfencedFrame::GetAdditionalStyleContext(int32_t aIndex
) const
635 int32_t openIndex
= -1;
636 int32_t closeIndex
= -1;
637 int32_t lastIndex
= mSeparatorsCount
-1;
641 openIndex
= lastIndex
;
645 closeIndex
= lastIndex
;
647 if (aIndex
< 0 || aIndex
> lastIndex
) {
651 if (aIndex
< mSeparatorsCount
) {
652 return mSeparatorsChar
[aIndex
].GetStyleContext();
654 else if (aIndex
== openIndex
) {
655 return mOpenChar
->GetStyleContext();
657 else if (aIndex
== closeIndex
) {
658 return mCloseChar
->GetStyleContext();
664 nsMathMLmfencedFrame::SetAdditionalStyleContext(int32_t aIndex
,
665 nsStyleContext
* aStyleContext
)
667 int32_t openIndex
= -1;
668 int32_t closeIndex
= -1;
669 int32_t lastIndex
= mSeparatorsCount
-1;
673 openIndex
= lastIndex
;
677 closeIndex
= lastIndex
;
679 if (aIndex
< 0 || aIndex
> lastIndex
) {
683 if (aIndex
< mSeparatorsCount
) {
684 mSeparatorsChar
[aIndex
].SetStyleContext(aStyleContext
);
686 else if (aIndex
== openIndex
) {
687 mOpenChar
->SetStyleContext(aStyleContext
);
689 else if (aIndex
== closeIndex
) {
690 mCloseChar
->SetStyleContext(aStyleContext
);