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