Bug 1800263 - Part 1: Tidup, make MarkingState a private enum inside GCMarker r=sfink
[gecko.git] / layout / mathml / nsMathMLmoFrame.cpp
blob5c21b0911ed4d772bbb8b70cd2fec60b292c60b0
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsMathMLmoFrame.h"
9 #include "gfxContext.h"
10 #include "mozilla/PresShell.h"
11 #include "nsCSSValue.h"
12 #include "nsLayoutUtils.h"
13 #include "nsPresContext.h"
14 #include "nsContentUtils.h"
15 #include "nsFrameSelection.h"
16 #include "mozilla/dom/MathMLElement.h"
17 #include <algorithm>
19 using namespace mozilla;
22 // <mo> -- operator, fence, or separator - implementation
25 nsIFrame* NS_NewMathMLmoFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
26 return new (aPresShell) nsMathMLmoFrame(aStyle, aPresShell->GetPresContext());
29 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmoFrame)
31 nsMathMLmoFrame::~nsMathMLmoFrame() = default;
33 static const char16_t kApplyFunction = char16_t(0x2061);
34 static const char16_t kInvisibleTimes = char16_t(0x2062);
35 static const char16_t kInvisibleSeparator = char16_t(0x2063);
36 static const char16_t kInvisiblePlus = char16_t(0x2064);
38 eMathMLFrameType nsMathMLmoFrame::GetMathMLFrameType() {
39 return NS_MATHML_OPERATOR_IS_INVISIBLE(mFlags)
40 ? eMathMLFrameType_OperatorInvisible
41 : eMathMLFrameType_OperatorOrdinary;
44 // since a mouse click implies selection, we cannot just rely on the
45 // frame's state bit in our child text frame. So we will first check
46 // its selected state bit, and use this little helper to double check.
47 bool nsMathMLmoFrame::IsFrameInSelection(nsIFrame* aFrame) {
48 NS_ASSERTION(aFrame, "null arg");
49 if (!aFrame || !aFrame->IsSelected()) return false;
51 const nsFrameSelection* frameSelection = aFrame->GetConstFrameSelection();
52 UniquePtr<SelectionDetails> details =
53 frameSelection->LookUpSelection(aFrame->GetContent(), 0, 1, true);
55 return details != nullptr;
58 bool nsMathMLmoFrame::UseMathMLChar() {
59 return (NS_MATHML_OPERATOR_GET_FORM(mFlags) &&
60 NS_MATHML_OPERATOR_IS_MUTABLE(mFlags)) ||
61 NS_MATHML_OPERATOR_IS_CENTERED(mFlags);
64 void nsMathMLmoFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
65 const nsDisplayListSet& aLists) {
66 bool useMathMLChar = UseMathMLChar();
68 if (!useMathMLChar) {
69 // let the base class do everything
70 nsMathMLTokenFrame::BuildDisplayList(aBuilder, aLists);
71 } else {
72 DisplayBorderBackgroundOutline(aBuilder, aLists);
74 // make our char selected if our inner child text frame is selected
75 bool isSelected = false;
76 nsRect selectedRect;
77 nsIFrame* firstChild = mFrames.FirstChild();
78 if (IsFrameInSelection(firstChild)) {
79 mMathMLChar.GetRect(selectedRect);
80 // add a one pixel border (it renders better for operators like minus)
81 selectedRect.Inflate(nsPresContext::CSSPixelsToAppUnits(1));
82 isSelected = true;
84 mMathMLChar.Display(aBuilder, this, aLists, 0,
85 isSelected ? &selectedRect : nullptr);
87 #if defined(DEBUG) && defined(SHOW_BOUNDING_BOX)
88 // for visual debug
89 DisplayBoundingMetrics(aBuilder, this, mReference, mBoundingMetrics,
90 aLists);
91 #endif
95 // get the text that we enclose and setup our nsMathMLChar
96 void nsMathMLmoFrame::ProcessTextData() {
97 mFlags = 0;
99 nsAutoString data;
100 nsContentUtils::GetNodeTextContent(mContent, false, data);
102 data.CompressWhitespace();
103 int32_t length = data.Length();
104 char16_t ch = (length == 0) ? char16_t('\0') : data[0];
106 if ((length == 1) && (ch == kApplyFunction || ch == kInvisibleSeparator ||
107 ch == kInvisiblePlus || ch == kInvisibleTimes)) {
108 mFlags |= NS_MATHML_OPERATOR_INVISIBLE;
111 // don't bother doing anything special if we don't have a single child
112 if (mFrames.GetLength() != 1) {
113 data.Truncate(); // empty data to reset the char
114 mMathMLChar.SetData(data);
115 mMathMLChar.SetComputedStyle(Style());
116 return;
119 // special... in math mode, the usual minus sign '-' looks too short, so
120 // what we do here is to remap <mo>-</mo> to the official Unicode minus
121 // sign (U+2212) which looks much better. For background on this, see
122 // http://groups.google.com/groups?hl=en&th=66488daf1ade7635&rnum=1
123 if (1 == length && ch == '-') {
124 ch = 0x2212;
125 data = ch;
128 // cache the special bits: mutable, accent, movablelimits, centered.
129 // we need to do this in anticipation of other requirements, and these
130 // bits don't change. Do not reset these bits unless the text gets changed.
132 // lookup all the forms under which the operator is listed in the dictionary,
133 // and record whether the operator has accent="true" or movablelimits="true"
134 nsOperatorFlags allFlags = 0;
135 for (const auto& form :
136 {NS_MATHML_OPERATOR_FORM_INFIX, NS_MATHML_OPERATOR_FORM_POSTFIX,
137 NS_MATHML_OPERATOR_FORM_PREFIX}) {
138 nsOperatorFlags flags = 0;
139 float dummy;
140 if (nsMathMLOperators::LookupOperator(data, form, &flags, &dummy, &dummy)) {
141 allFlags |= flags;
145 mFlags |= allFlags & NS_MATHML_OPERATOR_ACCENT;
146 mFlags |= allFlags & NS_MATHML_OPERATOR_MOVABLELIMITS;
148 // see if this is an operator that should be centered to cater for
149 // fonts that are not math-aware
150 if (1 == length) {
151 if ((ch == '+') || (ch == '=') || (ch == '*') ||
152 (ch == 0x2212) || // &minus;
153 (ch == 0x2264) || // &le;
154 (ch == 0x2265) || // &ge;
155 (ch == 0x00D7)) { // &times;
156 mFlags |= NS_MATHML_OPERATOR_CENTERED;
160 // cache the operator
161 mMathMLChar.SetData(data);
163 // cache the native direction -- beware of bug 133429...
164 // mEmbellishData.direction must always retain our native direction, whereas
165 // mMathMLChar.GetStretchDirection() may change later, when Stretch() is
166 // called
167 mEmbellishData.direction = mMathMLChar.GetStretchDirection();
169 bool isMutable =
170 NS_MATHML_OPERATOR_IS_LARGEOP(allFlags) ||
171 (mEmbellishData.direction != NS_STRETCH_DIRECTION_UNSUPPORTED);
172 if (isMutable) mFlags |= NS_MATHML_OPERATOR_MUTABLE;
174 mMathMLChar.SetComputedStyle(Style());
177 // get our 'form' and lookup in the Operator Dictionary to fetch
178 // our default data that may come from there. Then complete our setup
179 // using attributes that we may have. To stay in sync, this function is
180 // called very often. We depend on many things that may change around us.
181 // However, we re-use unchanged values.
182 void nsMathMLmoFrame::ProcessOperatorData() {
183 // if we have been here before, we will just use our cached form
184 uint8_t form = NS_MATHML_OPERATOR_GET_FORM(mFlags);
185 nsAutoString value;
186 float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
188 // special bits are always kept in mFlags.
189 // remember the mutable bit from ProcessTextData().
190 // Some chars are listed under different forms in the dictionary,
191 // and there could be a form under which the char is mutable.
192 // If the char is the core of an embellished container, we will keep
193 // it mutable irrespective of the form of the embellished container.
194 // Also remember the other special bits that we want to carry forward.
195 mFlags &= NS_MATHML_OPERATOR_MUTABLE | NS_MATHML_OPERATOR_ACCENT |
196 NS_MATHML_OPERATOR_MOVABLELIMITS | NS_MATHML_OPERATOR_CENTERED |
197 NS_MATHML_OPERATOR_INVISIBLE;
199 if (!mEmbellishData.coreFrame) {
200 // i.e., we haven't been here before, the default form is infix
201 form = NS_MATHML_OPERATOR_FORM_INFIX;
203 // reset everything so that we don't keep outdated values around
204 // in case of dynamic changes
205 mEmbellishData.flags = 0;
206 mEmbellishData.coreFrame = nullptr;
207 mEmbellishData.leadingSpace = 0;
208 mEmbellishData.trailingSpace = 0;
209 if (mMathMLChar.Length() != 1)
210 mEmbellishData.direction = NS_STRETCH_DIRECTION_UNSUPPORTED;
211 // else... retain the native direction obtained in ProcessTextData()
213 if (!mFrames.FirstChild()) {
214 return;
217 mEmbellishData.flags |= NS_MATHML_EMBELLISH_OPERATOR;
218 mEmbellishData.coreFrame = this;
220 // there are two particular things that we also need to record so that if
221 // our parent is <mover>, <munder>, or <munderover>, they will treat us
222 // properly: 1) do we have accent="true" 2) do we have movablelimits="true"
224 // they need the extra information to decide how to treat their
225 // scripts/limits (note: <mover>, <munder>, or <munderover> need not
226 // necessarily be our direct parent -- case of embellished operators)
228 // default values from the Operator Dictionary were obtained in
229 // ProcessTextData() and these special bits are always kept in mFlags
230 if (NS_MATHML_OPERATOR_IS_ACCENT(mFlags))
231 mEmbellishData.flags |= NS_MATHML_EMBELLISH_ACCENT;
232 if (NS_MATHML_OPERATOR_IS_MOVABLELIMITS(mFlags))
233 mEmbellishData.flags |= NS_MATHML_EMBELLISH_MOVABLELIMITS;
235 // see if the accent attribute is there
236 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::accent_,
237 value);
238 if (value.EqualsLiteral("true"))
239 mEmbellishData.flags |= NS_MATHML_EMBELLISH_ACCENT;
240 else if (value.EqualsLiteral("false"))
241 mEmbellishData.flags &= ~NS_MATHML_EMBELLISH_ACCENT;
243 // see if the movablelimits attribute is there
244 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::movablelimits_,
245 value);
246 if (value.EqualsLiteral("true"))
247 mEmbellishData.flags |= NS_MATHML_EMBELLISH_MOVABLELIMITS;
248 else if (value.EqualsLiteral("false"))
249 mEmbellishData.flags &= ~NS_MATHML_EMBELLISH_MOVABLELIMITS;
251 // ---------------------------------------------------------------------
252 // we will be called again to re-sync the rest of our state next time...
253 // (nobody needs the other values below at this stage)
254 mFlags |= form;
255 return;
258 nsPresContext* presContext = PresContext();
260 // beware of bug 133814 - there is a two-way dependency in the
261 // embellished hierarchy: our embellished ancestors need to set
262 // their flags based on some of our state (set above), and here we
263 // need to re-sync our 'form' depending on our outermost embellished
264 // container. A null form here means that an earlier attempt to stretch
265 // our mMathMLChar failed, in which case we don't bother re-stretching again
266 if (form) {
267 // get our outermost embellished container and its parent.
268 // (we ensure that we are the core, not just a sibling of the core)
269 nsIFrame* embellishAncestor = this;
270 nsEmbellishData embellishData;
271 nsIFrame* parentAncestor = this;
272 do {
273 embellishAncestor = parentAncestor;
274 parentAncestor = embellishAncestor->GetParent();
275 GetEmbellishDataFrom(parentAncestor, embellishData);
276 } while (embellishData.coreFrame == this);
278 // flag if we have an embellished ancestor
279 if (embellishAncestor != this)
280 mFlags |= NS_MATHML_OPERATOR_EMBELLISH_ANCESTOR;
281 else
282 mFlags &= ~NS_MATHML_OPERATOR_EMBELLISH_ANCESTOR;
284 // find the position of our outermost embellished container w.r.t
285 // its siblings.
287 nsIFrame* nextSibling = embellishAncestor->GetNextSibling();
288 nsIFrame* prevSibling = embellishAncestor->GetPrevSibling();
290 // flag to distinguish from a real infix. Set for (embellished) operators
291 // that live in (inferred) mrows.
292 nsIMathMLFrame* mathAncestor = do_QueryFrame(parentAncestor);
293 bool zeroSpacing = false;
294 if (mathAncestor) {
295 zeroSpacing = !mathAncestor->IsMrowLike();
296 } else {
297 nsMathMLmathBlockFrame* blockFrame = do_QueryFrame(parentAncestor);
298 if (blockFrame) {
299 zeroSpacing = !blockFrame->IsMrowLike();
302 if (zeroSpacing) {
303 mFlags |= NS_MATHML_OPERATOR_EMBELLISH_ISOLATED;
304 } else {
305 mFlags &= ~NS_MATHML_OPERATOR_EMBELLISH_ISOLATED;
308 // find our form
309 form = NS_MATHML_OPERATOR_FORM_INFIX;
310 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::form, value);
311 if (!value.IsEmpty()) {
312 if (value.EqualsLiteral("prefix"))
313 form = NS_MATHML_OPERATOR_FORM_PREFIX;
314 else if (value.EqualsLiteral("postfix"))
315 form = NS_MATHML_OPERATOR_FORM_POSTFIX;
316 } else {
317 // set our form flag depending on the position
318 if (!prevSibling && nextSibling)
319 form = NS_MATHML_OPERATOR_FORM_PREFIX;
320 else if (prevSibling && !nextSibling)
321 form = NS_MATHML_OPERATOR_FORM_POSTFIX;
323 mFlags &= ~NS_MATHML_OPERATOR_FORM; // clear the old form bits
324 mFlags |= form;
326 // Use the default value suggested by the MathML REC.
327 // http://www.w3.org/TR/MathML/chapter3.html#presm.mo.attrs
328 // thickmathspace = 5/18em
329 float lspace = 5.0f / 18.0f;
330 float rspace = 5.0f / 18.0f;
331 // lookup the operator dictionary
332 nsAutoString data;
333 mMathMLChar.GetData(data);
334 nsOperatorFlags flags = 0;
335 if (nsMathMLOperators::LookupOperatorWithFallback(data, form, &flags,
336 &lspace, &rspace)) {
337 mFlags &= ~NS_MATHML_OPERATOR_FORM; // clear the form bits
338 mFlags |= flags; // just add bits without overwriting
341 // Spacing is zero if our outermost embellished operator is not in an
342 // inferred mrow.
343 if (!NS_MATHML_OPERATOR_EMBELLISH_IS_ISOLATED(mFlags) &&
344 (lspace || rspace)) {
345 // Cache the default values of lspace and rspace.
346 // since these values are relative to the 'em' unit, convert to twips now
347 nscoord em;
348 RefPtr<nsFontMetrics> fm =
349 nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation);
350 GetEmHeight(fm, em);
352 mEmbellishData.leadingSpace = NSToCoordRound(lspace * em);
353 mEmbellishData.trailingSpace = NSToCoordRound(rspace * em);
355 // tuning if we don't want too much extra space when we are a script.
356 // (with its fonts, TeX sets lspace=0 & rspace=0 as soon as scriptlevel>0.
357 // Our fonts can be anything, so...)
358 if (StyleFont()->mMathDepth > 0 &&
359 !NS_MATHML_OPERATOR_HAS_EMBELLISH_ANCESTOR(mFlags)) {
360 mEmbellishData.leadingSpace /= 2;
361 mEmbellishData.trailingSpace /= 2;
366 // If we are an accent without explicit lspace="." or rspace=".",
367 // we will ignore our default leading/trailing space
369 // lspace
371 // "Specifies the leading space appearing before the operator"
373 // values: length
374 // default: set by dictionary (thickmathspace)
376 // XXXfredw Support for negative and relative values is not implemented
377 // (bug 805926).
378 // Relative values will give a multiple of the current leading space,
379 // which is not necessarily the default one.
381 nscoord leadingSpace = mEmbellishData.leadingSpace;
382 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::lspace_, value);
383 if (!value.IsEmpty()) {
384 nsCSSValue cssValue;
385 if (dom::MathMLElement::ParseNumericValue(value, cssValue, 0,
386 mContent->OwnerDoc())) {
387 if ((eCSSUnit_Number == cssValue.GetUnit()) && !cssValue.GetFloatValue())
388 leadingSpace = 0;
389 else if (cssValue.IsLengthUnit())
390 leadingSpace = CalcLength(presContext, mComputedStyle, cssValue,
391 fontSizeInflation);
392 mFlags |= NS_MATHML_OPERATOR_LSPACE_ATTR;
396 // rspace
398 // "Specifies the trailing space appearing after the operator"
400 // values: length
401 // default: set by dictionary (thickmathspace)
403 // XXXfredw Support for negative and relative values is not implemented
404 // (bug 805926).
405 // Relative values will give a multiple of the current leading space,
406 // which is not necessarily the default one.
408 nscoord trailingSpace = mEmbellishData.trailingSpace;
409 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::rspace_, value);
410 if (!value.IsEmpty()) {
411 nsCSSValue cssValue;
412 if (dom::MathMLElement::ParseNumericValue(value, cssValue, 0,
413 mContent->OwnerDoc())) {
414 if ((eCSSUnit_Number == cssValue.GetUnit()) && !cssValue.GetFloatValue())
415 trailingSpace = 0;
416 else if (cssValue.IsLengthUnit())
417 trailingSpace = CalcLength(presContext, mComputedStyle, cssValue,
418 fontSizeInflation);
419 mFlags |= NS_MATHML_OPERATOR_RSPACE_ATTR;
423 // little extra tuning to round lspace & rspace to at least a pixel so that
424 // operators don't look as if they are colliding with their operands
425 if (leadingSpace || trailingSpace) {
426 nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1);
427 if (leadingSpace && leadingSpace < onePixel) leadingSpace = onePixel;
428 if (trailingSpace && trailingSpace < onePixel) trailingSpace = onePixel;
431 // the values that we get from our attributes override the dictionary
432 mEmbellishData.leadingSpace = leadingSpace;
433 mEmbellishData.trailingSpace = trailingSpace;
435 // Now see if there are user-defined attributes that override the dictionary.
436 // XXX Bug 1197771 - forcing an attribute to true when it is false in the
437 // dictionary can cause conflicts in the rest of the stretching algorithms
438 // (e.g. all largeops are assumed to have a vertical direction)
440 // For each attribute overriden by the user, turn off its bit flag.
441 // symmetric|movablelimits|separator|largeop|accent|fence|stretchy|form
442 // special: accent and movablelimits are handled above,
443 // don't process them here
445 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::stretchy_,
446 value);
447 if (value.EqualsLiteral("false")) {
448 mFlags &= ~NS_MATHML_OPERATOR_STRETCHY;
449 } else if (value.EqualsLiteral("true")) {
450 mFlags |= NS_MATHML_OPERATOR_STRETCHY;
452 if (NS_MATHML_OPERATOR_IS_FENCE(mFlags)) {
453 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::fence_, value);
454 if (value.EqualsLiteral("false"))
455 mFlags &= ~NS_MATHML_OPERATOR_FENCE;
456 else
457 mEmbellishData.flags |= NS_MATHML_EMBELLISH_FENCE;
459 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::largeop_, value);
460 if (value.EqualsLiteral("false")) {
461 mFlags &= ~NS_MATHML_OPERATOR_LARGEOP;
462 } else if (value.EqualsLiteral("true")) {
463 mFlags |= NS_MATHML_OPERATOR_LARGEOP;
465 if (NS_MATHML_OPERATOR_IS_SEPARATOR(mFlags)) {
466 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::separator_,
467 value);
468 if (value.EqualsLiteral("false"))
469 mFlags &= ~NS_MATHML_OPERATOR_SEPARATOR;
470 else
471 mEmbellishData.flags |= NS_MATHML_EMBELLISH_SEPARATOR;
473 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::symmetric_,
474 value);
475 if (value.EqualsLiteral("false"))
476 mFlags &= ~NS_MATHML_OPERATOR_SYMMETRIC;
477 else if (value.EqualsLiteral("true"))
478 mFlags |= NS_MATHML_OPERATOR_SYMMETRIC;
480 // minsize
482 // "Specifies the minimum size of the operator when stretchy"
484 // values: length
485 // default: set by dictionary (1em)
487 // We don't allow negative values.
488 // Note: Contrary to other "length" values, unitless and percentage do not
489 // give a multiple of the defaut value but a multiple of the operator at
490 // normal size.
492 mMinSize = 0;
493 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::minsize_, value);
494 if (!value.IsEmpty()) {
495 nsCSSValue cssValue;
496 if (dom::MathMLElement::ParseNumericValue(value, cssValue, 0,
497 mContent->OwnerDoc())) {
498 nsCSSUnit unit = cssValue.GetUnit();
499 if (eCSSUnit_Number == unit)
500 mMinSize = cssValue.GetFloatValue();
501 else if (eCSSUnit_Percent == unit)
502 mMinSize = cssValue.GetPercentValue();
503 else if (eCSSUnit_Null != unit) {
504 mMinSize = float(CalcLength(presContext, mComputedStyle, cssValue,
505 fontSizeInflation));
506 mFlags |= NS_MATHML_OPERATOR_MINSIZE_ABSOLUTE;
511 // maxsize
513 // "Specifies the maximum size of the operator when stretchy"
515 // values: length | "infinity"
516 // default: set by dictionary (infinity)
518 // We don't allow negative values.
519 // Note: Contrary to other "length" values, unitless and percentage do not
520 // give a multiple of the defaut value but a multiple of the operator at
521 // normal size.
523 mMaxSize = NS_MATHML_OPERATOR_SIZE_INFINITY;
524 mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::maxsize_, value);
525 if (!value.IsEmpty()) {
526 nsCSSValue cssValue;
527 if (dom::MathMLElement::ParseNumericValue(value, cssValue, 0,
528 mContent->OwnerDoc())) {
529 nsCSSUnit unit = cssValue.GetUnit();
530 if (eCSSUnit_Number == unit)
531 mMaxSize = cssValue.GetFloatValue();
532 else if (eCSSUnit_Percent == unit)
533 mMaxSize = cssValue.GetPercentValue();
534 else if (eCSSUnit_Null != unit) {
535 mMaxSize = float(CalcLength(presContext, mComputedStyle, cssValue,
536 fontSizeInflation));
537 mFlags |= NS_MATHML_OPERATOR_MAXSIZE_ABSOLUTE;
543 static uint32_t GetStretchHint(nsOperatorFlags aFlags,
544 nsPresentationData aPresentationData,
545 bool aIsVertical,
546 const nsStyleFont* aStyleFont) {
547 uint32_t stretchHint = NS_STRETCH_NONE;
548 // See if it is okay to stretch,
549 // starting from what the Operator Dictionary said
550 if (NS_MATHML_OPERATOR_IS_MUTABLE(aFlags)) {
551 // set the largeop or largeopOnly flags to suitably cover all the
552 // 8 possible cases depending on whether displaystyle, largeop,
553 // stretchy are true or false (see bug 69325).
554 // . largeopOnly is taken if largeop=true and stretchy=false
555 // . largeop is taken if largeop=true and stretchy=true
556 if (aStyleFont->mMathStyle == StyleMathStyle::Normal &&
557 NS_MATHML_OPERATOR_IS_LARGEOP(aFlags)) {
558 stretchHint = NS_STRETCH_LARGEOP; // (largeopOnly, not mask!)
559 if (NS_MATHML_OPERATOR_IS_STRETCHY(aFlags)) {
560 stretchHint |= NS_STRETCH_NEARER | NS_STRETCH_LARGER;
562 } else if (NS_MATHML_OPERATOR_IS_STRETCHY(aFlags)) {
563 if (aIsVertical) {
564 // TeX hint. Can impact some sloppy markups missing <mrow></mrow>
565 stretchHint = NS_STRETCH_NEARER;
566 } else {
567 stretchHint = NS_STRETCH_NORMAL;
570 // else if the stretchy and largeop attributes have been disabled,
571 // the operator is not mutable
573 return stretchHint;
576 // NOTE: aDesiredStretchSize is an IN/OUT parameter
577 // On input - it contains our current size
578 // On output - the same size or the new size that we want
579 NS_IMETHODIMP
580 nsMathMLmoFrame::Stretch(DrawTarget* aDrawTarget,
581 nsStretchDirection aStretchDirection,
582 nsBoundingMetrics& aContainerSize,
583 ReflowOutput& aDesiredStretchSize) {
584 if (NS_MATHML_STRETCH_WAS_DONE(mPresentationData.flags)) {
585 NS_WARNING("it is wrong to fire stretch more than once on a frame");
586 return NS_OK;
588 mPresentationData.flags |= NS_MATHML_STRETCH_DONE;
590 nsIFrame* firstChild = mFrames.FirstChild();
592 // get the axis height;
593 float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
594 RefPtr<nsFontMetrics> fm =
595 nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation);
596 nscoord axisHeight, height;
597 GetAxisHeight(aDrawTarget, fm, axisHeight);
599 // get the leading to be left at the top and the bottom of the stretched char
600 // this seems more reliable than using fm->GetLeading() on suspicious fonts
601 nscoord em;
602 GetEmHeight(fm, em);
603 nscoord leading = NSToCoordRound(0.2f * em);
605 // Operators that are stretchy, or those that are to be centered
606 // to cater for fonts that are not math-aware, are handled by the MathMLChar
607 // ('form' is reset if stretch fails -- i.e., we don't bother to stretch next
608 // time)
609 bool useMathMLChar = UseMathMLChar();
611 nsBoundingMetrics charSize;
612 nsBoundingMetrics container = aDesiredStretchSize.mBoundingMetrics;
613 bool isVertical = false;
615 if (((aStretchDirection == NS_STRETCH_DIRECTION_VERTICAL) ||
616 (aStretchDirection == NS_STRETCH_DIRECTION_DEFAULT)) &&
617 (mEmbellishData.direction == NS_STRETCH_DIRECTION_VERTICAL)) {
618 isVertical = true;
621 uint32_t stretchHint =
622 GetStretchHint(mFlags, mPresentationData, isVertical, StyleFont());
624 if (useMathMLChar) {
625 nsBoundingMetrics initialSize = aDesiredStretchSize.mBoundingMetrics;
627 if (stretchHint != NS_STRETCH_NONE) {
628 container = aContainerSize;
630 // some adjustments if the operator is symmetric and vertical
632 if (isVertical && NS_MATHML_OPERATOR_IS_SYMMETRIC(mFlags)) {
633 // we need to center about the axis
634 nscoord delta = std::max(container.ascent - axisHeight,
635 container.descent + axisHeight);
636 container.ascent = delta + axisHeight;
637 container.descent = delta - axisHeight;
639 // get ready in case we encounter user-desired min-max size
640 delta = std::max(initialSize.ascent - axisHeight,
641 initialSize.descent + axisHeight);
642 initialSize.ascent = delta + axisHeight;
643 initialSize.descent = delta - axisHeight;
646 // check for user-desired min-max size
648 if (mMaxSize != NS_MATHML_OPERATOR_SIZE_INFINITY && mMaxSize > 0.0f) {
649 // if we are here, there is a user defined maxsize ...
650 // XXX Set stretchHint = NS_STRETCH_NORMAL? to honor the maxsize as
651 // close as possible?
652 if (NS_MATHML_OPERATOR_MAXSIZE_IS_ABSOLUTE(mFlags)) {
653 // there is an explicit value like maxsize="20pt"
654 // try to maintain the aspect ratio of the char
655 float aspect =
656 mMaxSize / float(initialSize.ascent + initialSize.descent);
657 container.ascent =
658 std::min(container.ascent, nscoord(initialSize.ascent * aspect));
659 container.descent = std::min(container.descent,
660 nscoord(initialSize.descent * aspect));
661 // below we use a type cast instead of a conversion to avoid a VC++
662 // bug see
663 // http://support.microsoft.com/support/kb/articles/Q115/7/05.ASP
664 container.width = std::min(container.width, (nscoord)mMaxSize);
665 } else { // multiplicative value
666 container.ascent = std::min(container.ascent,
667 nscoord(initialSize.ascent * mMaxSize));
668 container.descent = std::min(container.descent,
669 nscoord(initialSize.descent * mMaxSize));
670 container.width =
671 std::min(container.width, nscoord(initialSize.width * mMaxSize));
674 if (isVertical && !NS_MATHML_OPERATOR_IS_SYMMETRIC(mFlags)) {
675 // re-adjust to align the char with the bottom of the initial
676 // container
677 height = container.ascent + container.descent;
678 container.descent = aContainerSize.descent;
679 container.ascent = height - container.descent;
683 if (mMinSize > 0.0f) {
684 // if we are here, there is a user defined minsize ...
685 // always allow the char to stretch in its natural direction,
686 // even if it is different from the caller's direction
687 if (aStretchDirection != NS_STRETCH_DIRECTION_DEFAULT &&
688 aStretchDirection != mEmbellishData.direction) {
689 aStretchDirection = NS_STRETCH_DIRECTION_DEFAULT;
690 // but when we are not honoring the requested direction
691 // we should not use the caller's container size either
692 container = initialSize;
694 if (NS_MATHML_OPERATOR_MINSIZE_IS_ABSOLUTE(mFlags)) {
695 // there is an explicit value like minsize="20pt"
696 // try to maintain the aspect ratio of the char
697 float aspect =
698 mMinSize / float(initialSize.ascent + initialSize.descent);
699 container.ascent =
700 std::max(container.ascent, nscoord(initialSize.ascent * aspect));
701 container.descent = std::max(container.descent,
702 nscoord(initialSize.descent * aspect));
703 container.width = std::max(container.width, (nscoord)mMinSize);
704 } else { // multiplicative value
705 container.ascent = std::max(container.ascent,
706 nscoord(initialSize.ascent * mMinSize));
707 container.descent = std::max(container.descent,
708 nscoord(initialSize.descent * mMinSize));
709 container.width =
710 std::max(container.width, nscoord(initialSize.width * mMinSize));
713 if (isVertical && !NS_MATHML_OPERATOR_IS_SYMMETRIC(mFlags)) {
714 // re-adjust to align the char with the bottom of the initial
715 // container
716 height = container.ascent + container.descent;
717 container.descent = aContainerSize.descent;
718 container.ascent = height - container.descent;
723 // let the MathMLChar stretch itself...
724 nsresult res = mMathMLChar.Stretch(
725 this, aDrawTarget, fontSizeInflation, aStretchDirection, container,
726 charSize, stretchHint,
727 StyleVisibility()->mDirection == StyleDirection::Rtl);
728 if (NS_FAILED(res)) {
729 // gracefully handle cases where stretching the char failed (i.e.,
730 // GetBoundingMetrics failed) clear our 'form' to behave as if the
731 // operator wasn't in the dictionary
732 mFlags &= ~NS_MATHML_OPERATOR_FORM;
733 useMathMLChar = false;
737 // Place our children using the default method
738 // This will allow our child text frame to get its DidReflow()
739 nsresult rv = Place(aDrawTarget, true, aDesiredStretchSize);
740 if (NS_MATHML_HAS_ERROR(mPresentationData.flags) || NS_FAILED(rv)) {
741 // Make sure the child frames get their DidReflow() calls.
742 DidReflowChildren(mFrames.FirstChild());
745 if (useMathMLChar) {
746 // update our bounding metrics... it becomes that of our MathML char
747 mBoundingMetrics = charSize;
749 // if the returned direction is 'unsupported', the char didn't actually
750 // change. So we do the centering only if necessary
751 if (mMathMLChar.GetStretchDirection() != NS_STRETCH_DIRECTION_UNSUPPORTED ||
752 NS_MATHML_OPERATOR_IS_CENTERED(mFlags)) {
753 bool largeopOnly = (NS_STRETCH_LARGEOP & stretchHint) != 0 &&
754 (NS_STRETCH_VARIABLE_MASK & stretchHint) == 0;
756 if (isVertical || NS_MATHML_OPERATOR_IS_CENTERED(mFlags)) {
757 // the desired size returned by mMathMLChar maybe different
758 // from the size of the container.
759 // the mMathMLChar.mRect.y calculation is subtle, watch out!!!
761 height = mBoundingMetrics.ascent + mBoundingMetrics.descent;
762 if (NS_MATHML_OPERATOR_IS_SYMMETRIC(mFlags) ||
763 NS_MATHML_OPERATOR_IS_CENTERED(mFlags)) {
764 // For symmetric and vertical operators, or for operators that are
765 // always centered ('+', '*', etc) we want to center about the axis of
766 // the container
767 mBoundingMetrics.descent = height / 2 - axisHeight;
768 } else if (!largeopOnly) {
769 // Align the center of the char with the center of the container
770 mBoundingMetrics.descent =
771 height / 2 + (container.ascent + container.descent) / 2 -
772 container.ascent;
773 } // else align the baselines
774 mBoundingMetrics.ascent = height - mBoundingMetrics.descent;
779 // Fixup for the final height.
780 // On one hand, our stretchy height can sometimes be shorter than surrounding
781 // ASCII chars, e.g., arrow symbols have |mBoundingMetrics.ascent + leading|
782 // that is smaller than the ASCII's ascent, hence when painting the background
783 // later, it won't look uniform along the line.
784 // On the other hand, sometimes we may leave too much gap when our glyph
785 // happens to come from a font with tall glyphs. For example, since CMEX10 has
786 // very tall glyphs, its natural font metrics are large, even if we pick a
787 // small glyph whose size is comparable to the size of a normal ASCII glyph.
788 // So to avoid uneven spacing in either of these two cases, we use the height
789 // of the ASCII font as a reference and try to match it if possible.
791 // special case for accents... keep them short to improve mouse operations...
792 // an accent can only be the non-first child of <mover>, <munder>,
793 // <munderover>
794 bool isAccent = NS_MATHML_EMBELLISH_IS_ACCENT(mEmbellishData.flags);
795 if (isAccent) {
796 nsEmbellishData parentData;
797 GetEmbellishDataFrom(GetParent(), parentData);
798 isAccent = (NS_MATHML_EMBELLISH_IS_ACCENTOVER(parentData.flags) ||
799 NS_MATHML_EMBELLISH_IS_ACCENTUNDER(parentData.flags)) &&
800 parentData.coreFrame != this;
802 if (isAccent && firstChild) {
803 // see bug 188467 for what is going on here
804 nscoord dy = aDesiredStretchSize.BlockStartAscent() -
805 (mBoundingMetrics.ascent + leading);
806 aDesiredStretchSize.SetBlockStartAscent(mBoundingMetrics.ascent + leading);
807 aDesiredStretchSize.Height() =
808 aDesiredStretchSize.BlockStartAscent() + mBoundingMetrics.descent;
810 firstChild->SetPosition(firstChild->GetPosition() - nsPoint(0, dy));
811 } else if (useMathMLChar) {
812 nscoord ascent = fm->MaxAscent();
813 nscoord descent = fm->MaxDescent();
814 aDesiredStretchSize.SetBlockStartAscent(
815 std::max(mBoundingMetrics.ascent + leading, ascent));
816 aDesiredStretchSize.Height() =
817 aDesiredStretchSize.BlockStartAscent() +
818 std::max(mBoundingMetrics.descent + leading, descent);
820 aDesiredStretchSize.Width() = mBoundingMetrics.width;
821 aDesiredStretchSize.mBoundingMetrics = mBoundingMetrics;
822 mReference.x = 0;
823 mReference.y = aDesiredStretchSize.BlockStartAscent();
824 // Place our mMathMLChar, its origin is in our coordinate system
825 if (useMathMLChar) {
826 nscoord dy =
827 aDesiredStretchSize.BlockStartAscent() - mBoundingMetrics.ascent;
828 mMathMLChar.SetRect(
829 nsRect(0, dy, charSize.width, charSize.ascent + charSize.descent));
832 // Before we leave... there is a last item in the check-list:
833 // If our parent is not embellished, it means we are the outermost embellished
834 // container and so we put the spacing, otherwise we don't include the
835 // spacing, the outermost embellished container will take care of it.
837 if (!NS_MATHML_OPERATOR_HAS_EMBELLISH_ANCESTOR(mFlags)) {
838 // Account the spacing if we are not an accent with explicit attributes
839 nscoord leadingSpace = mEmbellishData.leadingSpace;
840 if (isAccent && !NS_MATHML_OPERATOR_HAS_LSPACE_ATTR(mFlags)) {
841 leadingSpace = 0;
843 nscoord trailingSpace = mEmbellishData.trailingSpace;
844 if (isAccent && !NS_MATHML_OPERATOR_HAS_RSPACE_ATTR(mFlags)) {
845 trailingSpace = 0;
848 mBoundingMetrics.width += leadingSpace + trailingSpace;
849 aDesiredStretchSize.Width() = mBoundingMetrics.width;
850 aDesiredStretchSize.mBoundingMetrics.width = mBoundingMetrics.width;
852 nscoord dx = StyleVisibility()->mDirection == StyleDirection::Rtl
853 ? trailingSpace
854 : leadingSpace;
855 if (dx) {
856 // adjust the offsets
857 mBoundingMetrics.leftBearing += dx;
858 mBoundingMetrics.rightBearing += dx;
859 aDesiredStretchSize.mBoundingMetrics.leftBearing += dx;
860 aDesiredStretchSize.mBoundingMetrics.rightBearing += dx;
862 if (useMathMLChar) {
863 nsRect rect;
864 mMathMLChar.GetRect(rect);
865 mMathMLChar.SetRect(
866 nsRect(rect.x + dx, rect.y, rect.width, rect.height));
867 } else {
868 nsIFrame* childFrame = firstChild;
869 while (childFrame) {
870 childFrame->SetPosition(childFrame->GetPosition() + nsPoint(dx, 0));
871 childFrame = childFrame->GetNextSibling();
877 // Finished with these:
878 ClearSavedChildMetrics();
879 // Set our overflow area
880 GatherAndStoreOverflow(&aDesiredStretchSize);
882 // There used to be code here to change the height of the child frame to
883 // change the caret height, but the text frame that manages the caret is now
884 // not a direct child but wrapped in a block frame. See also bug 412033.
886 return NS_OK;
889 NS_IMETHODIMP
890 nsMathMLmoFrame::InheritAutomaticData(nsIFrame* aParent) {
891 // retain our native direction, it only changes if our text content changes
892 nsStretchDirection direction = mEmbellishData.direction;
893 nsMathMLTokenFrame::InheritAutomaticData(aParent);
894 ProcessTextData();
895 mEmbellishData.direction = direction;
896 return NS_OK;
899 NS_IMETHODIMP
900 nsMathMLmoFrame::TransmitAutomaticData() {
901 // this will cause us to re-sync our flags from scratch
902 // but our returned 'form' is still not final (bug 133429), it will
903 // be recomputed to its final value during the next call in Reflow()
904 mEmbellishData.coreFrame = nullptr;
905 ProcessOperatorData();
906 return NS_OK;
909 void nsMathMLmoFrame::SetInitialChildList(ChildListID aListID,
910 nsFrameList&& aChildList) {
911 // First, let the parent class do its work
912 nsMathMLTokenFrame::SetInitialChildList(aListID, std::move(aChildList));
913 ProcessTextData();
916 void nsMathMLmoFrame::Reflow(nsPresContext* aPresContext,
917 ReflowOutput& aDesiredSize,
918 const ReflowInput& aReflowInput,
919 nsReflowStatus& aStatus) {
920 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
922 // certain values use units that depend on our ComputedStyle, so
923 // it is safer to just process the whole lot here
924 ProcessOperatorData();
926 nsMathMLTokenFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
929 nsresult nsMathMLmoFrame::Place(DrawTarget* aDrawTarget, bool aPlaceOrigin,
930 ReflowOutput& aDesiredSize) {
931 nsresult rv =
932 nsMathMLTokenFrame::Place(aDrawTarget, aPlaceOrigin, aDesiredSize);
934 if (NS_FAILED(rv)) {
935 return rv;
938 /* Special behaviour for largeops.
939 In MathML "stretchy" and displaystyle "largeop" are different notions,
940 even if we use the same technique to draw them (picking size variants).
941 So largeop display operators should be considered "non-stretchy" and
942 thus their sizes should be taken into account for the stretch size of
943 other elements.
945 This is a preliminary stretch - exact sizing/placement is handled by the
946 Stretch() method.
949 if (!aPlaceOrigin && StyleFont()->mMathStyle == StyleMathStyle::Normal &&
950 NS_MATHML_OPERATOR_IS_LARGEOP(mFlags) && UseMathMLChar()) {
951 nsBoundingMetrics newMetrics;
952 rv = mMathMLChar.Stretch(
953 this, aDrawTarget, nsLayoutUtils::FontSizeInflationFor(this),
954 NS_STRETCH_DIRECTION_VERTICAL, aDesiredSize.mBoundingMetrics,
955 newMetrics, NS_STRETCH_LARGEOP,
956 StyleVisibility()->mDirection == StyleDirection::Rtl);
958 if (NS_FAILED(rv)) {
959 // Just use the initial size
960 return NS_OK;
963 aDesiredSize.mBoundingMetrics = newMetrics;
964 /* Treat the ascent/descent values calculated in the TokenFrame place
965 calculations as the minimum for aDesiredSize calculations, rather
966 than fetching them from font metrics again.
968 aDesiredSize.SetBlockStartAscent(
969 std::max(mBoundingMetrics.ascent, newMetrics.ascent));
970 aDesiredSize.Height() =
971 aDesiredSize.BlockStartAscent() +
972 std::max(mBoundingMetrics.descent, newMetrics.descent);
973 aDesiredSize.Width() = newMetrics.width;
974 mBoundingMetrics = newMetrics;
976 return NS_OK;
979 /* virtual */
980 void nsMathMLmoFrame::MarkIntrinsicISizesDirty() {
981 // if we get this, it may mean that something changed in the text
982 // content. So blow away everything an re-build the automatic data
983 // from the parent of our outermost embellished container (we ensure
984 // that we are the core, not just a sibling of the core)
986 ProcessTextData();
988 nsIFrame* target = this;
989 nsEmbellishData embellishData;
990 do {
991 target = target->GetParent();
992 GetEmbellishDataFrom(target, embellishData);
993 } while (embellishData.coreFrame == this);
995 // we have automatic data to update in the children of the target frame
996 // XXXldb This should really be marking dirty rather than rebuilding
997 // so that we don't rebuild multiple times for the same change.
998 RebuildAutomaticDataForChildren(target);
1000 nsMathMLContainerFrame::MarkIntrinsicISizesDirty();
1003 /* virtual */
1004 void nsMathMLmoFrame::GetIntrinsicISizeMetrics(gfxContext* aRenderingContext,
1005 ReflowOutput& aDesiredSize) {
1006 ProcessOperatorData();
1007 if (UseMathMLChar()) {
1008 uint32_t stretchHint =
1009 GetStretchHint(mFlags, mPresentationData, true, StyleFont());
1010 aDesiredSize.Width() = mMathMLChar.GetMaxWidth(
1011 this, aRenderingContext->GetDrawTarget(),
1012 nsLayoutUtils::FontSizeInflationFor(this), stretchHint);
1013 } else {
1014 nsMathMLTokenFrame::GetIntrinsicISizeMetrics(aRenderingContext,
1015 aDesiredSize);
1018 // leadingSpace and trailingSpace are actually applied to the outermost
1019 // embellished container but for determining total intrinsic width it should
1020 // be safe to include it for the core here instead.
1021 bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl;
1022 aDesiredSize.Width() +=
1023 mEmbellishData.leadingSpace + mEmbellishData.trailingSpace;
1024 aDesiredSize.mBoundingMetrics.width = aDesiredSize.Width();
1025 if (isRTL) {
1026 aDesiredSize.mBoundingMetrics.leftBearing += mEmbellishData.trailingSpace;
1027 aDesiredSize.mBoundingMetrics.rightBearing += mEmbellishData.trailingSpace;
1028 } else {
1029 aDesiredSize.mBoundingMetrics.leftBearing += mEmbellishData.leadingSpace;
1030 aDesiredSize.mBoundingMetrics.rightBearing += mEmbellishData.leadingSpace;
1034 nsresult nsMathMLmoFrame::AttributeChanged(int32_t aNameSpaceID,
1035 nsAtom* aAttribute,
1036 int32_t aModType) {
1037 // check if this is an attribute that can affect the embellished hierarchy
1038 // in a significant way and re-layout the entire hierarchy.
1039 if (nsGkAtoms::accent_ == aAttribute ||
1040 nsGkAtoms::movablelimits_ == aAttribute) {
1041 // set the target as the parent of our outermost embellished container
1042 // (we ensure that we are the core, not just a sibling of the core)
1043 nsIFrame* target = this;
1044 nsEmbellishData embellishData;
1045 do {
1046 target = target->GetParent();
1047 GetEmbellishDataFrom(target, embellishData);
1048 } while (embellishData.coreFrame == this);
1050 // we have automatic data to update in the children of the target frame
1051 return ReLayoutChildren(target);
1054 return nsMathMLTokenFrame::AttributeChanged(aNameSpaceID, aAttribute,
1055 aModType);
1058 void nsMathMLmoFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) {
1059 nsMathMLTokenFrame::DidSetComputedStyle(aOldStyle);
1060 mMathMLChar.SetComputedStyle(Style());