/src/mozilla-central/layout/mathml/nsMathMLmunderoverFrame.cpp
Line | Count | Source (jump to first uncovered line) |
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/. */ |
6 | | |
7 | | #include "nsMathMLmunderoverFrame.h" |
8 | | #include "nsPresContext.h" |
9 | | #include "nsMathMLmmultiscriptsFrame.h" |
10 | | #include "nsMathMLElement.h" |
11 | | #include <algorithm> |
12 | | #include "gfxContext.h" |
13 | | #include "gfxMathTable.h" |
14 | | |
15 | | // |
16 | | // <munderover> -- attach an underscript-overscript pair to a base - implementation |
17 | | // <mover> -- attach an overscript to a base - implementation |
18 | | // <munder> -- attach an underscript to a base - implementation |
19 | | // |
20 | | |
21 | | nsIFrame* |
22 | | NS_NewMathMLmunderoverFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle) |
23 | 0 | { |
24 | 0 | return new (aPresShell) nsMathMLmunderoverFrame(aStyle); |
25 | 0 | } |
26 | | |
27 | | NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmunderoverFrame) |
28 | | |
29 | | nsMathMLmunderoverFrame::~nsMathMLmunderoverFrame() |
30 | 0 | { |
31 | 0 | } |
32 | | |
33 | | nsresult |
34 | | nsMathMLmunderoverFrame::AttributeChanged(int32_t aNameSpaceID, |
35 | | nsAtom* aAttribute, |
36 | | int32_t aModType) |
37 | 0 | { |
38 | 0 | if (nsGkAtoms::accent_ == aAttribute || |
39 | 0 | nsGkAtoms::accentunder_ == aAttribute) { |
40 | 0 | // When we have automatic data to update within ourselves, we ask our |
41 | 0 | // parent to re-layout its children |
42 | 0 | return ReLayoutChildren(GetParent()); |
43 | 0 | } |
44 | 0 | |
45 | 0 | return nsMathMLContainerFrame:: |
46 | 0 | AttributeChanged(aNameSpaceID, aAttribute, aModType); |
47 | 0 | } |
48 | | |
49 | | NS_IMETHODIMP |
50 | | nsMathMLmunderoverFrame::UpdatePresentationData(uint32_t aFlagsValues, |
51 | | uint32_t aFlagsToUpdate) |
52 | 0 | { |
53 | 0 | nsMathMLContainerFrame::UpdatePresentationData(aFlagsValues, aFlagsToUpdate); |
54 | 0 | // disable the stretch-all flag if we are going to act like a subscript-superscript pair |
55 | 0 | if (NS_MATHML_EMBELLISH_IS_MOVABLELIMITS(mEmbellishData.flags) && |
56 | 0 | StyleFont()->mMathDisplay == NS_MATHML_DISPLAYSTYLE_INLINE) { |
57 | 0 | mPresentationData.flags &= ~NS_MATHML_STRETCH_ALL_CHILDREN_HORIZONTALLY; |
58 | 0 | } |
59 | 0 | else { |
60 | 0 | mPresentationData.flags |= NS_MATHML_STRETCH_ALL_CHILDREN_HORIZONTALLY; |
61 | 0 | } |
62 | 0 | return NS_OK; |
63 | 0 | } |
64 | | |
65 | | NS_IMETHODIMP |
66 | | nsMathMLmunderoverFrame::InheritAutomaticData(nsIFrame* aParent) |
67 | 0 | { |
68 | 0 | // let the base class get the default from our parent |
69 | 0 | nsMathMLContainerFrame::InheritAutomaticData(aParent); |
70 | 0 |
|
71 | 0 | mPresentationData.flags |= NS_MATHML_STRETCH_ALL_CHILDREN_HORIZONTALLY; |
72 | 0 |
|
73 | 0 | return NS_OK; |
74 | 0 | } |
75 | | |
76 | | void |
77 | | nsMathMLmunderoverFrame::DestroyFrom(nsIFrame* aDestroyRoot, PostDestroyData& aPostDestroyData) |
78 | 0 | { |
79 | 0 | if (!mPostReflowIncrementScriptLevelCommands.IsEmpty()) { |
80 | 0 | PresShell()->CancelReflowCallback(this); |
81 | 0 | } |
82 | 0 | nsMathMLContainerFrame::DestroyFrom(aDestroyRoot, aPostDestroyData); |
83 | 0 | } |
84 | | |
85 | | uint8_t |
86 | | nsMathMLmunderoverFrame::ScriptIncrement(nsIFrame* aFrame) |
87 | 0 | { |
88 | 0 | nsIFrame* child = mFrames.FirstChild(); |
89 | 0 | if (!aFrame || aFrame == child) { |
90 | 0 | return 0; |
91 | 0 | } |
92 | 0 | child = child->GetNextSibling(); |
93 | 0 | if (aFrame == child) { |
94 | 0 | if (mContent->IsMathMLElement(nsGkAtoms::mover_)) { |
95 | 0 | return mIncrementOver ? 1 : 0; |
96 | 0 | } |
97 | 0 | return mIncrementUnder ? 1 : 0; |
98 | 0 | } |
99 | 0 | if (child && aFrame == child->GetNextSibling()) { |
100 | 0 | // must be a over frame of munderover |
101 | 0 | return mIncrementOver ? 1 : 0; |
102 | 0 | } |
103 | 0 | return 0; // frame not found |
104 | 0 | } |
105 | | |
106 | | void |
107 | | nsMathMLmunderoverFrame::SetIncrementScriptLevel(uint32_t aChildIndex, |
108 | | bool aIncrement) |
109 | 0 | { |
110 | 0 | nsIFrame* child = PrincipalChildList().FrameAt(aChildIndex); |
111 | 0 | if (!child || !child->GetContent()->IsMathMLElement() || |
112 | 0 | child->GetContent()->GetPrimaryFrame() != child) { |
113 | 0 | return; |
114 | 0 | } |
115 | 0 | |
116 | 0 | auto element = static_cast<nsMathMLElement*>(child->GetContent()); |
117 | 0 | if (element->GetIncrementScriptLevel() == aIncrement) { |
118 | 0 | return; |
119 | 0 | } |
120 | 0 | |
121 | 0 | if (mPostReflowIncrementScriptLevelCommands.IsEmpty()) { |
122 | 0 | PresShell()->PostReflowCallback(this); |
123 | 0 | } |
124 | 0 |
|
125 | 0 | mPostReflowIncrementScriptLevelCommands.AppendElement( |
126 | 0 | SetIncrementScriptLevelCommand { aChildIndex, aIncrement }); |
127 | 0 | } |
128 | | |
129 | | bool |
130 | | nsMathMLmunderoverFrame::ReflowFinished() |
131 | 0 | { |
132 | 0 | SetPendingPostReflowIncrementScriptLevel(); |
133 | 0 | return true; |
134 | 0 | } |
135 | | |
136 | | void |
137 | | nsMathMLmunderoverFrame::ReflowCallbackCanceled() |
138 | 0 | { |
139 | 0 | // Do nothing, at this point our work will just be useless. |
140 | 0 | mPostReflowIncrementScriptLevelCommands.Clear(); |
141 | 0 | } |
142 | | |
143 | | void |
144 | | nsMathMLmunderoverFrame::SetPendingPostReflowIncrementScriptLevel() |
145 | 0 | { |
146 | 0 | MOZ_ASSERT(!mPostReflowIncrementScriptLevelCommands.IsEmpty()); |
147 | 0 |
|
148 | 0 | nsTArray<SetIncrementScriptLevelCommand> commands; |
149 | 0 | commands.SwapElements(mPostReflowIncrementScriptLevelCommands); |
150 | 0 |
|
151 | 0 | for (const auto& command : commands) { |
152 | 0 | nsIFrame* child = PrincipalChildList().FrameAt(command.mChildIndex); |
153 | 0 | if (!child || !child->GetContent()->IsMathMLElement()) { |
154 | 0 | continue; |
155 | 0 | } |
156 | 0 | |
157 | 0 | auto element = static_cast<nsMathMLElement*>(child->GetContent()); |
158 | 0 | element->SetIncrementScriptLevel(command.mDoIncrement, true); |
159 | 0 | } |
160 | 0 | } |
161 | | |
162 | | NS_IMETHODIMP |
163 | | nsMathMLmunderoverFrame::TransmitAutomaticData() |
164 | 0 | { |
165 | 0 | // At this stage, all our children are in sync and we can fully |
166 | 0 | // resolve our own mEmbellishData struct |
167 | 0 | //--------------------------------------------------------------------- |
168 | 0 |
|
169 | 0 | /* |
170 | 0 | The REC says: |
171 | 0 |
|
172 | 0 | As regards munder (respectively mover) : |
173 | 0 | The default value of accentunder is false, unless underscript |
174 | 0 | is an <mo> element or an embellished operator. If underscript is |
175 | 0 | an <mo> element, the value of its accent attribute is used as the |
176 | 0 | default value of accentunder. If underscript is an embellished |
177 | 0 | operator, the accent attribute of the <mo> element at its |
178 | 0 | core is used as the default value. As with all attributes, an |
179 | 0 | explicitly given value overrides the default. |
180 | 0 |
|
181 | 0 | XXX The winner is the outermost setting in conflicting settings like these: |
182 | 0 | <munder accentunder='true'> |
183 | 0 | <mi>...</mi> |
184 | 0 | <mo accentunder='false'> ... </mo> |
185 | 0 | </munder> |
186 | 0 |
|
187 | 0 | As regards munderover: |
188 | 0 | The accent and accentunder attributes have the same effect as |
189 | 0 | the attributes with the same names on <mover> and <munder>, |
190 | 0 | respectively. Their default values are also computed in the |
191 | 0 | same manner as described for those elements, with the default |
192 | 0 | value of accent depending on overscript and the default value |
193 | 0 | of accentunder depending on underscript. |
194 | 0 | */ |
195 | 0 |
|
196 | 0 | nsIFrame* overscriptFrame = nullptr; |
197 | 0 | nsIFrame* underscriptFrame = nullptr; |
198 | 0 | nsIFrame* baseFrame = mFrames.FirstChild(); |
199 | 0 |
|
200 | 0 | if (baseFrame) { |
201 | 0 | if (mContent->IsAnyOfMathMLElements(nsGkAtoms::munder_, |
202 | 0 | nsGkAtoms::munderover_)) { |
203 | 0 | underscriptFrame = baseFrame->GetNextSibling(); |
204 | 0 | } else { |
205 | 0 | NS_ASSERTION(mContent->IsMathMLElement(nsGkAtoms::mover_), |
206 | 0 | "mContent->NodeInfo()->NameAtom() not recognized"); |
207 | 0 | overscriptFrame = baseFrame->GetNextSibling(); |
208 | 0 | } |
209 | 0 | } |
210 | 0 | if (underscriptFrame && |
211 | 0 | mContent->IsMathMLElement(nsGkAtoms::munderover_)) { |
212 | 0 | overscriptFrame = underscriptFrame->GetNextSibling(); |
213 | 0 |
|
214 | 0 | } |
215 | 0 |
|
216 | 0 | // if our base is an embellished operator, let its state bubble to us (in particular, |
217 | 0 | // this is where we get the flag for NS_MATHML_EMBELLISH_MOVABLELIMITS). Our flags |
218 | 0 | // are reset to the default values of false if the base frame isn't embellished. |
219 | 0 | mPresentationData.baseFrame = baseFrame; |
220 | 0 | GetEmbellishDataFrom(baseFrame, mEmbellishData); |
221 | 0 |
|
222 | 0 | // The default value of accentunder is false, unless the underscript is embellished |
223 | 0 | // and its core <mo> is an accent |
224 | 0 | nsEmbellishData embellishData; |
225 | 0 | nsAutoString value; |
226 | 0 | if (mContent->IsAnyOfMathMLElements(nsGkAtoms::munder_, |
227 | 0 | nsGkAtoms::munderover_)) { |
228 | 0 | GetEmbellishDataFrom(underscriptFrame, embellishData); |
229 | 0 | if (NS_MATHML_EMBELLISH_IS_ACCENT(embellishData.flags)) { |
230 | 0 | mEmbellishData.flags |= NS_MATHML_EMBELLISH_ACCENTUNDER; |
231 | 0 | } else { |
232 | 0 | mEmbellishData.flags &= ~NS_MATHML_EMBELLISH_ACCENTUNDER; |
233 | 0 | } |
234 | 0 |
|
235 | 0 | // if we have an accentunder attribute, it overrides what the underscript said |
236 | 0 | if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::accentunder_, value)) { |
237 | 0 | if (value.EqualsLiteral("true")) { |
238 | 0 | mEmbellishData.flags |= NS_MATHML_EMBELLISH_ACCENTUNDER; |
239 | 0 | } else if (value.EqualsLiteral("false")) { |
240 | 0 | mEmbellishData.flags &= ~NS_MATHML_EMBELLISH_ACCENTUNDER; |
241 | 0 | } |
242 | 0 | } |
243 | 0 | } |
244 | 0 |
|
245 | 0 | // The default value of accent is false, unless the overscript is embellished |
246 | 0 | // and its core <mo> is an accent |
247 | 0 | if (mContent->IsAnyOfMathMLElements(nsGkAtoms::mover_, |
248 | 0 | nsGkAtoms::munderover_)) { |
249 | 0 | GetEmbellishDataFrom(overscriptFrame, embellishData); |
250 | 0 | if (NS_MATHML_EMBELLISH_IS_ACCENT(embellishData.flags)) { |
251 | 0 | mEmbellishData.flags |= NS_MATHML_EMBELLISH_ACCENTOVER; |
252 | 0 | } else { |
253 | 0 | mEmbellishData.flags &= ~NS_MATHML_EMBELLISH_ACCENTOVER; |
254 | 0 | } |
255 | 0 |
|
256 | 0 | // if we have an accent attribute, it overrides what the overscript said |
257 | 0 | if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::accent_, value)) { |
258 | 0 | if (value.EqualsLiteral("true")) { |
259 | 0 | mEmbellishData.flags |= NS_MATHML_EMBELLISH_ACCENTOVER; |
260 | 0 | } else if (value.EqualsLiteral("false")) { |
261 | 0 | mEmbellishData.flags &= ~NS_MATHML_EMBELLISH_ACCENTOVER; |
262 | 0 | } |
263 | 0 | } |
264 | 0 | } |
265 | 0 |
|
266 | 0 | bool subsupDisplay = |
267 | 0 | NS_MATHML_EMBELLISH_IS_MOVABLELIMITS(mEmbellishData.flags) && |
268 | 0 | StyleFont()->mMathDisplay == NS_MATHML_DISPLAYSTYLE_INLINE; |
269 | 0 |
|
270 | 0 | // disable the stretch-all flag if we are going to act like a superscript |
271 | 0 | if (subsupDisplay) { |
272 | 0 | mPresentationData.flags &= ~NS_MATHML_STRETCH_ALL_CHILDREN_HORIZONTALLY; |
273 | 0 | } |
274 | 0 |
|
275 | 0 | // Now transmit any change that we want to our children so that they |
276 | 0 | // can update their mPresentationData structs |
277 | 0 | //--------------------------------------------------------------------- |
278 | 0 |
|
279 | 0 | /* The REC says: |
280 | 0 | Within underscript, <munderover> always sets displaystyle to "false", |
281 | 0 | but increments scriptlevel by 1 only when accentunder is "false". |
282 | 0 |
|
283 | 0 | Within overscript, <munderover> always sets displaystyle to "false", |
284 | 0 | but increments scriptlevel by 1 only when accent is "false". |
285 | 0 |
|
286 | 0 | Within subscript and superscript it increments scriptlevel by 1, and |
287 | 0 | sets displaystyle to "false", but leaves both attributes unchanged within |
288 | 0 | base. |
289 | 0 |
|
290 | 0 | The TeXBook treats 'over' like a superscript, so p.141 or Rule 13a |
291 | 0 | say it shouldn't be compressed. However, The TeXBook says |
292 | 0 | that math accents and \overline change uncramped styles to their |
293 | 0 | cramped counterparts. |
294 | 0 | */ |
295 | 0 | if (mContent->IsAnyOfMathMLElements(nsGkAtoms::mover_, |
296 | 0 | nsGkAtoms::munderover_)) { |
297 | 0 | uint32_t compress = NS_MATHML_EMBELLISH_IS_ACCENTOVER(mEmbellishData.flags) |
298 | 0 | ? NS_MATHML_COMPRESSED : 0; |
299 | 0 | mIncrementOver = |
300 | 0 | !NS_MATHML_EMBELLISH_IS_ACCENTOVER(mEmbellishData.flags) || |
301 | 0 | subsupDisplay; |
302 | 0 | SetIncrementScriptLevel( |
303 | 0 | mContent->IsMathMLElement(nsGkAtoms::mover_) ? 1 : 2, mIncrementOver); |
304 | 0 | if (mIncrementOver) { |
305 | 0 | PropagateFrameFlagFor(overscriptFrame, |
306 | 0 | NS_FRAME_MATHML_SCRIPT_DESCENDANT); |
307 | 0 | } |
308 | 0 | PropagatePresentationDataFor(overscriptFrame, compress, compress); |
309 | 0 | } |
310 | 0 | /* |
311 | 0 | The TeXBook treats 'under' like a subscript, so p.141 or Rule 13a |
312 | 0 | say it should be compressed |
313 | 0 | */ |
314 | 0 | if (mContent->IsAnyOfMathMLElements(nsGkAtoms::munder_, |
315 | 0 | nsGkAtoms::munderover_)) { |
316 | 0 | mIncrementUnder = |
317 | 0 | !NS_MATHML_EMBELLISH_IS_ACCENTUNDER(mEmbellishData.flags) || |
318 | 0 | subsupDisplay; |
319 | 0 | SetIncrementScriptLevel(1, mIncrementUnder); |
320 | 0 | if (mIncrementUnder) { |
321 | 0 | PropagateFrameFlagFor(underscriptFrame, |
322 | 0 | NS_FRAME_MATHML_SCRIPT_DESCENDANT); |
323 | 0 | } |
324 | 0 | PropagatePresentationDataFor(underscriptFrame, |
325 | 0 | NS_MATHML_COMPRESSED, |
326 | 0 | NS_MATHML_COMPRESSED); |
327 | 0 | } |
328 | 0 |
|
329 | 0 | /* Set flags for dtls font feature settings. |
330 | 0 |
|
331 | 0 | dtls |
332 | 0 | Dotless Forms |
333 | 0 | This feature provides dotless forms for Math Alphanumeric |
334 | 0 | characters, such as U+1D422 MATHEMATICAL BOLD SMALL I, |
335 | 0 | U+1D423 MATHEMATICAL BOLD SMALL J, U+1D456 |
336 | 0 | U+MATHEMATICAL ITALIC SMALL I, U+1D457 MATHEMATICAL ITALIC |
337 | 0 | SMALL J, and so on. |
338 | 0 | The dotless forms are to be used as base forms for placing |
339 | 0 | mathematical accents over them. |
340 | 0 |
|
341 | 0 | To opt out of this change, add the following to the stylesheet: |
342 | 0 | "font-feature-settings: 'dtls' 0" |
343 | 0 | */ |
344 | 0 | if (overscriptFrame && |
345 | 0 | NS_MATHML_EMBELLISH_IS_ACCENTOVER(mEmbellishData.flags) && |
346 | 0 | !NS_MATHML_EMBELLISH_IS_MOVABLELIMITS(mEmbellishData.flags)) { |
347 | 0 | PropagatePresentationDataFor(baseFrame, NS_MATHML_DTLS, NS_MATHML_DTLS); |
348 | 0 | } |
349 | 0 |
|
350 | 0 | return NS_OK; |
351 | 0 | } |
352 | | |
353 | | /* |
354 | | The REC says: |
355 | | * If the base is an operator with movablelimits="true" (or an embellished |
356 | | operator whose <mo> element core has movablelimits="true"), and |
357 | | displaystyle="false", then underscript and overscript are drawn in |
358 | | a subscript and superscript position, respectively. In this case, |
359 | | the accent and accentunder attributes are ignored. This is often |
360 | | used for limits on symbols such as ∑. |
361 | | |
362 | | i.e.,: |
363 | | if (NS_MATHML_EMBELLISH_IS_MOVABLELIMITS(mEmbellishDataflags) && |
364 | | StyleFont()->mMathDisplay == NS_MATHML_DISPLAYSTYLE_INLINE) { |
365 | | // place like subscript-superscript pair |
366 | | } |
367 | | else { |
368 | | // place like underscript-overscript pair |
369 | | } |
370 | | */ |
371 | | |
372 | | /* virtual */ nsresult |
373 | | nsMathMLmunderoverFrame::Place(DrawTarget* aDrawTarget, |
374 | | bool aPlaceOrigin, |
375 | | ReflowOutput& aDesiredSize) |
376 | 0 | { |
377 | 0 | float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this); |
378 | 0 | if (NS_MATHML_EMBELLISH_IS_MOVABLELIMITS(mEmbellishData.flags) && |
379 | 0 | StyleFont()->mMathDisplay == NS_MATHML_DISPLAYSTYLE_INLINE) { |
380 | 0 | //place like sub sup or subsup |
381 | 0 | if (mContent->IsMathMLElement(nsGkAtoms::munderover_)) { |
382 | 0 | return nsMathMLmmultiscriptsFrame::PlaceMultiScript(PresContext(), |
383 | 0 | aDrawTarget, |
384 | 0 | aPlaceOrigin, |
385 | 0 | aDesiredSize, |
386 | 0 | this, 0, 0, |
387 | 0 | fontSizeInflation); |
388 | 0 | } else if (mContent->IsMathMLElement( nsGkAtoms::munder_)) { |
389 | 0 | return nsMathMLmmultiscriptsFrame::PlaceMultiScript(PresContext(), |
390 | 0 | aDrawTarget, |
391 | 0 | aPlaceOrigin, |
392 | 0 | aDesiredSize, |
393 | 0 | this, 0, 0, |
394 | 0 | fontSizeInflation); |
395 | 0 | } else { |
396 | 0 | NS_ASSERTION(mContent->IsMathMLElement(nsGkAtoms::mover_), |
397 | 0 | "mContent->NodeInfo()->NameAtom() not recognized"); |
398 | 0 | return nsMathMLmmultiscriptsFrame::PlaceMultiScript(PresContext(), |
399 | 0 | aDrawTarget, |
400 | 0 | aPlaceOrigin, |
401 | 0 | aDesiredSize, |
402 | 0 | this, 0, 0, |
403 | 0 | fontSizeInflation); |
404 | 0 | } |
405 | 0 |
|
406 | 0 | } |
407 | 0 |
|
408 | 0 | //////////////////////////////////// |
409 | 0 | // Get the children's desired sizes |
410 | 0 |
|
411 | 0 | nsBoundingMetrics bmBase, bmUnder, bmOver; |
412 | 0 | ReflowOutput baseSize(aDesiredSize.GetWritingMode()); |
413 | 0 | ReflowOutput underSize(aDesiredSize.GetWritingMode()); |
414 | 0 | ReflowOutput overSize(aDesiredSize.GetWritingMode()); |
415 | 0 | nsIFrame* overFrame = nullptr; |
416 | 0 | nsIFrame* underFrame = nullptr; |
417 | 0 | nsIFrame* baseFrame = mFrames.FirstChild(); |
418 | 0 | underSize.SetBlockStartAscent(0); |
419 | 0 | overSize.SetBlockStartAscent(0); |
420 | 0 | bool haveError = false; |
421 | 0 | if (baseFrame) { |
422 | 0 | if (mContent->IsAnyOfMathMLElements(nsGkAtoms::munder_, |
423 | 0 | nsGkAtoms::munderover_)) { |
424 | 0 | underFrame = baseFrame->GetNextSibling(); |
425 | 0 | } else if (mContent->IsMathMLElement(nsGkAtoms::mover_)) { |
426 | 0 | overFrame = baseFrame->GetNextSibling(); |
427 | 0 | } |
428 | 0 | } |
429 | 0 | if (underFrame && mContent->IsMathMLElement(nsGkAtoms::munderover_)) { |
430 | 0 | overFrame = underFrame->GetNextSibling(); |
431 | 0 | } |
432 | 0 |
|
433 | 0 | if (mContent->IsMathMLElement(nsGkAtoms::munder_)) { |
434 | 0 | if (!baseFrame || !underFrame || underFrame->GetNextSibling()) { |
435 | 0 | // report an error, encourage people to get their markups in order |
436 | 0 | haveError = true; |
437 | 0 | } |
438 | 0 | } |
439 | 0 | if (mContent->IsMathMLElement(nsGkAtoms::mover_)) { |
440 | 0 | if (!baseFrame || !overFrame || overFrame->GetNextSibling()) { |
441 | 0 | // report an error, encourage people to get their markups in order |
442 | 0 | haveError = true; |
443 | 0 | } |
444 | 0 | } |
445 | 0 | if (mContent->IsMathMLElement(nsGkAtoms::munderover_)) { |
446 | 0 | if (!baseFrame || !underFrame || !overFrame || overFrame->GetNextSibling()) { |
447 | 0 | // report an error, encourage people to get their markups in order |
448 | 0 | haveError = true; |
449 | 0 | } |
450 | 0 | } |
451 | 0 | if (haveError) { |
452 | 0 | if (aPlaceOrigin) { |
453 | 0 | ReportChildCountError(); |
454 | 0 | } |
455 | 0 | return ReflowError(aDrawTarget, aDesiredSize); |
456 | 0 | } |
457 | 0 | GetReflowAndBoundingMetricsFor(baseFrame, baseSize, bmBase); |
458 | 0 | if (underFrame) { |
459 | 0 | GetReflowAndBoundingMetricsFor(underFrame, underSize, bmUnder); |
460 | 0 | } |
461 | 0 | if (overFrame) { |
462 | 0 | GetReflowAndBoundingMetricsFor(overFrame, overSize, bmOver); |
463 | 0 | } |
464 | 0 |
|
465 | 0 | nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1); |
466 | 0 |
|
467 | 0 | //////////////////// |
468 | 0 | // Place Children |
469 | 0 |
|
470 | 0 | RefPtr<nsFontMetrics> fm = |
471 | 0 | nsLayoutUtils::GetFontMetricsForFrame(this, fontSizeInflation); |
472 | 0 |
|
473 | 0 | nscoord xHeight = fm->XHeight(); |
474 | 0 | nscoord oneDevPixel = fm->AppUnitsPerDevPixel(); |
475 | 0 | gfxFont* mathFont = fm->GetThebesFontGroup()->GetFirstMathFont(); |
476 | 0 |
|
477 | 0 | nscoord ruleThickness; |
478 | 0 | GetRuleThickness (aDrawTarget, fm, ruleThickness); |
479 | 0 |
|
480 | 0 | nscoord correction = 0; |
481 | 0 | GetItalicCorrection (bmBase, correction); |
482 | 0 |
|
483 | 0 | // there are 2 different types of placement depending on |
484 | 0 | // whether we want an accented under or not |
485 | 0 |
|
486 | 0 | nscoord underDelta1 = 0; // gap between base and underscript |
487 | 0 | nscoord underDelta2 = 0; // extra space beneath underscript |
488 | 0 |
|
489 | 0 | if (!NS_MATHML_EMBELLISH_IS_ACCENTUNDER(mEmbellishData.flags)) { |
490 | 0 | // Rule 13a, App. G, TeXbook |
491 | 0 | nscoord bigOpSpacing2, bigOpSpacing4, bigOpSpacing5, dummy; |
492 | 0 | GetBigOpSpacings (fm, |
493 | 0 | dummy, bigOpSpacing2, |
494 | 0 | dummy, bigOpSpacing4, |
495 | 0 | bigOpSpacing5); |
496 | 0 | if (mathFont) { |
497 | 0 | // XXXfredw The Open Type MATH table has some StretchStack* parameters |
498 | 0 | // that we may use when the base is a stretchy horizontal operator. See |
499 | 0 | // bug 963131. |
500 | 0 | bigOpSpacing2 = |
501 | 0 | mathFont->MathTable()->Constant(gfxMathTable::LowerLimitGapMin, |
502 | 0 | oneDevPixel); |
503 | 0 | bigOpSpacing4 = |
504 | 0 | mathFont->MathTable()->Constant(gfxMathTable::LowerLimitBaselineDropMin, |
505 | 0 | oneDevPixel); |
506 | 0 | bigOpSpacing5 = 0; |
507 | 0 | } |
508 | 0 | underDelta1 = std::max(bigOpSpacing2, (bigOpSpacing4 - bmUnder.ascent)); |
509 | 0 | underDelta2 = bigOpSpacing5; |
510 | 0 | } |
511 | 0 | else { |
512 | 0 | // No corresponding rule in TeXbook - we are on our own here |
513 | 0 | // XXX tune the gap delta between base and underscript |
514 | 0 | // XXX Should we use Rule 10 like \underline does? |
515 | 0 | // XXXfredw Perhaps use the Underbar* parameters of the MATH table. See |
516 | 0 | // bug 963125. |
517 | 0 | underDelta1 = ruleThickness + onePixel/2; |
518 | 0 | underDelta2 = ruleThickness; |
519 | 0 | } |
520 | 0 | // empty under? |
521 | 0 | if (!(bmUnder.ascent + bmUnder.descent)) { |
522 | 0 | underDelta1 = 0; |
523 | 0 | underDelta2 = 0; |
524 | 0 | } |
525 | 0 |
|
526 | 0 | nscoord overDelta1 = 0; // gap between base and overscript |
527 | 0 | nscoord overDelta2 = 0; // extra space above overscript |
528 | 0 |
|
529 | 0 | if (!NS_MATHML_EMBELLISH_IS_ACCENTOVER(mEmbellishData.flags)) { |
530 | 0 | // Rule 13a, App. G, TeXbook |
531 | 0 | // XXXfredw The Open Type MATH table has some StretchStack* parameters |
532 | 0 | // that we may use when the base is a stretchy horizontal operator. See |
533 | 0 | // bug 963131. |
534 | 0 | nscoord bigOpSpacing1, bigOpSpacing3, bigOpSpacing5, dummy; |
535 | 0 | GetBigOpSpacings (fm, |
536 | 0 | bigOpSpacing1, dummy, |
537 | 0 | bigOpSpacing3, dummy, |
538 | 0 | bigOpSpacing5); |
539 | 0 | if (mathFont) { |
540 | 0 | // XXXfredw The Open Type MATH table has some StretchStack* parameters |
541 | 0 | // that we may use when the base is a stretchy horizontal operator. See |
542 | 0 | // bug 963131. |
543 | 0 | bigOpSpacing1 = |
544 | 0 | mathFont->MathTable()->Constant(gfxMathTable::UpperLimitGapMin, |
545 | 0 | oneDevPixel); |
546 | 0 | bigOpSpacing3 = |
547 | 0 | mathFont->MathTable()->Constant(gfxMathTable::UpperLimitBaselineRiseMin, |
548 | 0 | oneDevPixel); |
549 | 0 | bigOpSpacing5 = 0; |
550 | 0 | } |
551 | 0 | overDelta1 = std::max(bigOpSpacing1, (bigOpSpacing3 - bmOver.descent)); |
552 | 0 | overDelta2 = bigOpSpacing5; |
553 | 0 |
|
554 | 0 | // XXX This is not a TeX rule... |
555 | 0 | // delta1 (as computed abvove) can become really big when bmOver.descent is |
556 | 0 | // negative, e.g., if the content is &OverBar. In such case, we use the height |
557 | 0 | if (bmOver.descent < 0) |
558 | 0 | overDelta1 = std::max(bigOpSpacing1, (bigOpSpacing3 - (bmOver.ascent + bmOver.descent))); |
559 | 0 | } |
560 | 0 | else { |
561 | 0 | // Rule 12, App. G, TeXbook |
562 | 0 | // We are going to modify this rule to make it more general. |
563 | 0 | // The idea behind Rule 12 in the TeXBook is to keep the accent |
564 | 0 | // as close to the base as possible, while ensuring that the |
565 | 0 | // distance between the *baseline* of the accent char and |
566 | 0 | // the *baseline* of the base is atleast x-height. |
567 | 0 | // The idea is that for normal use, we would like all the accents |
568 | 0 | // on a line to line up atleast x-height above the baseline |
569 | 0 | // if possible. |
570 | 0 | // When the ascent of the base is >= x-height, |
571 | 0 | // the baseline of the accent char is placed just above the base |
572 | 0 | // (specifically, the baseline of the accent char is placed |
573 | 0 | // above the baseline of the base by the ascent of the base). |
574 | 0 | // For ease of implementation, |
575 | 0 | // this assumes that the font-designer designs accents |
576 | 0 | // in such a way that the bottom of the accent is atleast x-height |
577 | 0 | // above its baseline, otherwise there will be collisions |
578 | 0 | // with the base. Also there should be proper padding between |
579 | 0 | // the bottom of the accent char and its baseline. |
580 | 0 | // The above rule may not be obvious from a first |
581 | 0 | // reading of rule 12 in the TeXBook !!! |
582 | 0 | // The mathml <mover> tag can use accent chars that |
583 | 0 | // do not follow this convention. So we modify TeX's rule |
584 | 0 | // so that TeX's rule gets subsumed for accents that follow |
585 | 0 | // TeX's convention, |
586 | 0 | // while also allowing accents that do not follow the convention : |
587 | 0 | // we try to keep the *bottom* of the accent char atleast x-height |
588 | 0 | // from the baseline of the base char. we also slap on an extra |
589 | 0 | // padding between the accent and base chars. |
590 | 0 | overDelta1 = ruleThickness + onePixel/2; |
591 | 0 | nscoord accentBaseHeight = xHeight; |
592 | 0 | if (mathFont) { |
593 | 0 | accentBaseHeight = |
594 | 0 | mathFont->MathTable()->Constant(gfxMathTable::AccentBaseHeight, |
595 | 0 | oneDevPixel); |
596 | 0 | } |
597 | 0 | if (bmBase.ascent < accentBaseHeight) { |
598 | 0 | // also ensure at least accentBaseHeight above the baseline of the base |
599 | 0 | overDelta1 += accentBaseHeight - bmBase.ascent; |
600 | 0 | } |
601 | 0 | overDelta2 = ruleThickness; |
602 | 0 | } |
603 | 0 | // empty over? |
604 | 0 | if (!(bmOver.ascent + bmOver.descent)) { |
605 | 0 | overDelta1 = 0; |
606 | 0 | overDelta2 = 0; |
607 | 0 | } |
608 | 0 |
|
609 | 0 | nscoord dxBase = 0, dxOver = 0, dxUnder = 0; |
610 | 0 | nsAutoString valueAlign; |
611 | 0 | enum { |
612 | 0 | center, |
613 | 0 | left, |
614 | 0 | right |
615 | 0 | } alignPosition = center; |
616 | 0 |
|
617 | 0 | if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::align, valueAlign)) { |
618 | 0 | if (valueAlign.EqualsLiteral("left")) { |
619 | 0 | alignPosition = left; |
620 | 0 | } else if (valueAlign.EqualsLiteral("right")) { |
621 | 0 | alignPosition = right; |
622 | 0 | } |
623 | 0 | } |
624 | 0 |
|
625 | 0 | ////////// |
626 | 0 | // pass 1, do what <mover> does: attach the overscript on the base |
627 | 0 |
|
628 | 0 | // Ad-hoc - This is to override fonts which have ready-made _accent_ |
629 | 0 | // glyphs with negative lbearing and rbearing. We want to position |
630 | 0 | // the overscript ourselves |
631 | 0 | nscoord overWidth = bmOver.width; |
632 | 0 | if (!overWidth && (bmOver.rightBearing - bmOver.leftBearing > 0)) { |
633 | 0 | overWidth = bmOver.rightBearing - bmOver.leftBearing; |
634 | 0 | dxOver = -bmOver.leftBearing; |
635 | 0 | } |
636 | 0 |
|
637 | 0 | if (NS_MATHML_EMBELLISH_IS_ACCENTOVER(mEmbellishData.flags)) { |
638 | 0 | mBoundingMetrics.width = bmBase.width; |
639 | 0 | if (alignPosition == center) { |
640 | 0 | dxOver += correction; |
641 | 0 | } |
642 | 0 | } |
643 | 0 | else { |
644 | 0 | mBoundingMetrics.width = std::max(bmBase.width, overWidth); |
645 | 0 | if (alignPosition == center) { |
646 | 0 | dxOver += correction/2; |
647 | 0 | } |
648 | 0 | } |
649 | 0 |
|
650 | 0 | if (alignPosition == center) { |
651 | 0 | dxOver += (mBoundingMetrics.width - overWidth)/2; |
652 | 0 | dxBase = (mBoundingMetrics.width - bmBase.width)/2; |
653 | 0 | } else if (alignPosition == right) { |
654 | 0 | dxOver += mBoundingMetrics.width - overWidth; |
655 | 0 | dxBase = mBoundingMetrics.width - bmBase.width; |
656 | 0 | } |
657 | 0 |
|
658 | 0 | mBoundingMetrics.ascent = |
659 | 0 | bmBase.ascent + overDelta1 + bmOver.ascent + bmOver.descent; |
660 | 0 | mBoundingMetrics.descent = bmBase.descent; |
661 | 0 | mBoundingMetrics.leftBearing = |
662 | 0 | std::min(dxBase + bmBase.leftBearing, dxOver + bmOver.leftBearing); |
663 | 0 | mBoundingMetrics.rightBearing = |
664 | 0 | std::max(dxBase + bmBase.rightBearing, dxOver + bmOver.rightBearing); |
665 | 0 |
|
666 | 0 | ////////// |
667 | 0 | // pass 2, do what <munder> does: attach the underscript on the previous |
668 | 0 | // result. We conceptually view the previous result as an "anynomous base" |
669 | 0 | // from where to attach the underscript. Hence if the underscript is empty, |
670 | 0 | // we should end up like <mover>. If the overscript is empty, we should |
671 | 0 | // end up like <munder>. |
672 | 0 |
|
673 | 0 | nsBoundingMetrics bmAnonymousBase = mBoundingMetrics; |
674 | 0 | nscoord ascentAnonymousBase = |
675 | 0 | std::max(mBoundingMetrics.ascent + overDelta2, |
676 | 0 | overSize.BlockStartAscent() + bmOver.descent + |
677 | 0 | overDelta1 + bmBase.ascent); |
678 | 0 | ascentAnonymousBase = std::max(ascentAnonymousBase, |
679 | 0 | baseSize.BlockStartAscent()); |
680 | 0 |
|
681 | 0 | // Width of non-spacing marks is zero so use left and right bearing. |
682 | 0 | nscoord underWidth = bmUnder.width; |
683 | 0 | if (!underWidth) { |
684 | 0 | underWidth = bmUnder.rightBearing - bmUnder.leftBearing; |
685 | 0 | dxUnder = -bmUnder.leftBearing; |
686 | 0 | } |
687 | 0 |
|
688 | 0 | nscoord maxWidth = std::max(bmAnonymousBase.width, underWidth); |
689 | 0 | if (alignPosition == center && |
690 | 0 | !NS_MATHML_EMBELLISH_IS_ACCENTUNDER(mEmbellishData.flags)) { |
691 | 0 | GetItalicCorrection(bmAnonymousBase, correction); |
692 | 0 | dxUnder += -correction/2; |
693 | 0 | } |
694 | 0 | nscoord dxAnonymousBase = 0; |
695 | 0 | if (alignPosition == center) { |
696 | 0 | dxUnder += (maxWidth - underWidth)/2; |
697 | 0 | dxAnonymousBase = (maxWidth - bmAnonymousBase.width)/2; |
698 | 0 | } else if (alignPosition == right) { |
699 | 0 | dxUnder += maxWidth - underWidth; |
700 | 0 | dxAnonymousBase = maxWidth - bmAnonymousBase.width; |
701 | 0 | } |
702 | 0 |
|
703 | 0 | // adjust the offsets of the real base and overscript since their |
704 | 0 | // final offsets should be relative to us... |
705 | 0 | dxOver += dxAnonymousBase; |
706 | 0 | dxBase += dxAnonymousBase; |
707 | 0 |
|
708 | 0 | mBoundingMetrics.width = |
709 | 0 | std::max(dxAnonymousBase + bmAnonymousBase.width, dxUnder + bmUnder.width); |
710 | 0 | // At this point, mBoundingMetrics.ascent = bmAnonymousBase.ascent |
711 | 0 | mBoundingMetrics.descent = |
712 | 0 | bmAnonymousBase.descent + underDelta1 + bmUnder.ascent + bmUnder.descent; |
713 | 0 | mBoundingMetrics.leftBearing = |
714 | 0 | std::min(dxAnonymousBase + bmAnonymousBase.leftBearing, dxUnder + bmUnder.leftBearing); |
715 | 0 | mBoundingMetrics.rightBearing = |
716 | 0 | std::max(dxAnonymousBase + bmAnonymousBase.rightBearing, dxUnder + bmUnder.rightBearing); |
717 | 0 |
|
718 | 0 | aDesiredSize.SetBlockStartAscent(ascentAnonymousBase); |
719 | 0 | aDesiredSize.Height() = aDesiredSize.BlockStartAscent() + |
720 | 0 | std::max(mBoundingMetrics.descent + underDelta2, |
721 | 0 | bmAnonymousBase.descent + underDelta1 + bmUnder.ascent + |
722 | 0 | underSize.Height() - underSize.BlockStartAscent()); |
723 | 0 | aDesiredSize.Height() = std::max(aDesiredSize.Height(), |
724 | 0 | aDesiredSize.BlockStartAscent() + |
725 | 0 | baseSize.Height() - baseSize.BlockStartAscent()); |
726 | 0 | aDesiredSize.Width() = mBoundingMetrics.width; |
727 | 0 | aDesiredSize.mBoundingMetrics = mBoundingMetrics; |
728 | 0 |
|
729 | 0 | mReference.x = 0; |
730 | 0 | mReference.y = aDesiredSize.BlockStartAscent(); |
731 | 0 |
|
732 | 0 | if (aPlaceOrigin) { |
733 | 0 | nscoord dy; |
734 | 0 | // place overscript |
735 | 0 | if (overFrame) { |
736 | 0 | dy = aDesiredSize.BlockStartAscent() - |
737 | 0 | mBoundingMetrics.ascent + bmOver.ascent - |
738 | 0 | overSize.BlockStartAscent(); |
739 | 0 | FinishReflowChild (overFrame, PresContext(), overSize, nullptr, dxOver, dy, 0); |
740 | 0 | } |
741 | 0 | // place base |
742 | 0 | dy = aDesiredSize.BlockStartAscent() - baseSize.BlockStartAscent(); |
743 | 0 | FinishReflowChild (baseFrame, PresContext(), baseSize, nullptr, dxBase, dy, 0); |
744 | 0 | // place underscript |
745 | 0 | if (underFrame) { |
746 | 0 | dy = aDesiredSize.BlockStartAscent() + |
747 | 0 | mBoundingMetrics.descent - bmUnder.descent - |
748 | 0 | underSize.BlockStartAscent(); |
749 | 0 | FinishReflowChild (underFrame, PresContext(), underSize, nullptr, |
750 | 0 | dxUnder, dy, 0); |
751 | 0 | } |
752 | 0 | } |
753 | 0 | return NS_OK; |
754 | 0 | } |