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 "gfxContext.h"
8 #include "nsMathMLmtableFrame.h"
9 #include "nsPresContext.h"
10 #include "nsStyleConsts.h"
11 #include "nsNameSpaceManager.h"
12 #include "nsCSSRendering.h"
13 #include "mozilla/dom/MathMLElement.h"
17 #include "nsTableFrame.h"
20 #include "mozilla/PresShell.h"
21 #include "mozilla/RestyleManager.h"
24 #include "nsIScriptError.h"
25 #include "nsContentUtils.h"
26 #include "nsLayoutUtils.h"
28 using namespace mozilla
;
29 using namespace mozilla::image
;
30 using mozilla::dom::Element
;
33 // <mtable> -- table or matrix - implementation
36 static int8_t ParseStyleValue(nsAtom
* aAttribute
,
37 const nsAString
& aAttributeValue
) {
38 if (aAttribute
== nsGkAtoms::rowalign_
) {
39 if (aAttributeValue
.EqualsLiteral("top")) {
40 return static_cast<int8_t>(StyleVerticalAlignKeyword::Top
);
42 if (aAttributeValue
.EqualsLiteral("bottom")) {
43 return static_cast<int8_t>(StyleVerticalAlignKeyword::Bottom
);
45 if (aAttributeValue
.EqualsLiteral("center")) {
46 return static_cast<int8_t>(StyleVerticalAlignKeyword::Middle
);
48 return static_cast<int8_t>(StyleVerticalAlignKeyword::Baseline
);
51 if (aAttribute
== nsGkAtoms::columnalign_
) {
52 if (aAttributeValue
.EqualsLiteral("left")) {
53 return int8_t(StyleTextAlign::Left
);
55 if (aAttributeValue
.EqualsLiteral("right")) {
56 return int8_t(StyleTextAlign::Right
);
58 return int8_t(StyleTextAlign::Center
);
61 if (aAttribute
== nsGkAtoms::rowlines_
||
62 aAttribute
== nsGkAtoms::columnlines_
) {
63 if (aAttributeValue
.EqualsLiteral("solid")) {
64 return static_cast<int8_t>(StyleBorderStyle::Solid
);
66 if (aAttributeValue
.EqualsLiteral("dashed")) {
67 return static_cast<int8_t>(StyleBorderStyle::Dashed
);
69 return static_cast<int8_t>(StyleBorderStyle::None
);
72 MOZ_CRASH("Unrecognized attribute.");
76 static nsTArray
<int8_t>* ExtractStyleValues(const nsAString
& aString
,
78 bool aAllowMultiValues
) {
79 nsTArray
<int8_t>* styleArray
= nullptr;
81 const char16_t
* start
= aString
.BeginReading();
82 const char16_t
* end
= aString
.EndReading();
84 int32_t startIndex
= 0;
88 // Skip leading spaces.
89 while ((start
< end
) && nsCRT::IsAsciiSpace(*start
)) {
94 // Look for the end of the string, or another space.
95 while ((start
< end
) && !nsCRT::IsAsciiSpace(*start
)) {
100 // Grab the value found and process it.
103 styleArray
= new nsTArray
<int8_t>();
106 // We want to return a null array if an attribute gives multiple values,
107 // but multiple values aren't allowed.
108 if (styleArray
->Length() > 1 && !aAllowMultiValues
) {
113 nsDependentSubstring
valueString(aString
, startIndex
, count
);
114 int8_t styleValue
= ParseStyleValue(aAttribute
, valueString
);
115 styleArray
->AppendElement(styleValue
);
124 static nsresult
ReportParseError(nsIFrame
* aFrame
, const char16_t
* aAttribute
,
125 const char16_t
* aValue
) {
126 nsIContent
* content
= aFrame
->GetContent();
128 AutoTArray
<nsString
, 3> params
;
129 params
.AppendElement(aValue
);
130 params
.AppendElement(aAttribute
);
131 params
.AppendElement(nsDependentAtomString(content
->NodeInfo()->NameAtom()));
133 return nsContentUtils::ReportToConsole(
134 nsIScriptError::errorFlag
, "Layout: MathML"_ns
, content
->OwnerDoc(),
135 nsContentUtils::eMATHML_PROPERTIES
, "AttributeParsingError", params
);
138 // Each rowalign='top bottom' or columnalign='left right center' (from
139 // <mtable> or <mtr>) is split once into an nsTArray<int8_t> which is
140 // stored in the property table. Row/Cell frames query the property table
141 // to see what values apply to them.
143 NS_DECLARE_FRAME_PROPERTY_DELETABLE(RowAlignProperty
, nsTArray
<int8_t>)
144 NS_DECLARE_FRAME_PROPERTY_DELETABLE(RowLinesProperty
, nsTArray
<int8_t>)
145 NS_DECLARE_FRAME_PROPERTY_DELETABLE(ColumnAlignProperty
, nsTArray
<int8_t>)
146 NS_DECLARE_FRAME_PROPERTY_DELETABLE(ColumnLinesProperty
, nsTArray
<int8_t>)
148 static const FramePropertyDescriptor
<nsTArray
<int8_t>>* AttributeToProperty(
149 nsAtom
* aAttribute
) {
150 if (aAttribute
== nsGkAtoms::rowalign_
) {
151 return RowAlignProperty();
153 if (aAttribute
== nsGkAtoms::rowlines_
) {
154 return RowLinesProperty();
156 if (aAttribute
== nsGkAtoms::columnalign_
) {
157 return ColumnAlignProperty();
159 NS_ASSERTION(aAttribute
== nsGkAtoms::columnlines_
, "Invalid attribute");
160 return ColumnLinesProperty();
163 /* This method looks for a property that applies to a cell, but it looks
164 * recursively because some cell properties can come from the cell, a row,
165 * a table, etc. This function searches through the hierarchy for a property
166 * and returns its value. The function stops searching after checking a <mtable>
169 static nsTArray
<int8_t>* FindCellProperty(
170 const nsIFrame
* aCellFrame
,
171 const FramePropertyDescriptor
<nsTArray
<int8_t>>* aFrameProperty
) {
172 const nsIFrame
* currentFrame
= aCellFrame
;
173 nsTArray
<int8_t>* propertyData
= nullptr;
175 while (currentFrame
) {
176 propertyData
= currentFrame
->GetProperty(aFrameProperty
);
177 bool frameIsTable
= (currentFrame
->IsTableFrame());
179 if (propertyData
|| frameIsTable
) {
180 currentFrame
= nullptr; // A null frame pointer exits the loop
182 currentFrame
= currentFrame
->GetParent(); // Go to the parent frame
189 static void ApplyBorderToStyle(const nsMathMLmtdFrame
* aFrame
,
190 nsStyleBorder
& aStyleBorder
) {
191 uint32_t rowIndex
= aFrame
->RowIndex();
192 uint32_t columnIndex
= aFrame
->ColIndex();
194 nscoord borderWidth
= nsPresContext::CSSPixelsToAppUnits(1);
196 nsTArray
<int8_t>* rowLinesList
= FindCellProperty(aFrame
, RowLinesProperty());
198 nsTArray
<int8_t>* columnLinesList
=
199 FindCellProperty(aFrame
, ColumnLinesProperty());
201 const auto a2d
= aFrame
->PresContext()->AppUnitsPerDevPixel();
203 // We don't place a row line on top of the first row
204 if (rowIndex
> 0 && rowLinesList
) {
205 // If the row number is greater than the number of provided rowline
206 // values, we simply repeat the last value.
207 uint32_t listLength
= rowLinesList
->Length();
208 if (rowIndex
< listLength
) {
209 aStyleBorder
.SetBorderStyle(
211 static_cast<StyleBorderStyle
>(rowLinesList
->ElementAt(rowIndex
- 1)));
213 aStyleBorder
.SetBorderStyle(eSideTop
,
214 static_cast<StyleBorderStyle
>(
215 rowLinesList
->ElementAt(listLength
- 1)));
217 aStyleBorder
.SetBorderWidth(eSideTop
, borderWidth
, a2d
);
220 // We don't place a column line on the left of the first column.
221 if (columnIndex
> 0 && columnLinesList
) {
222 // If the column number is greater than the number of provided columline
223 // values, we simply repeat the last value.
224 uint32_t listLength
= columnLinesList
->Length();
225 if (columnIndex
< listLength
) {
226 aStyleBorder
.SetBorderStyle(
227 eSideLeft
, static_cast<StyleBorderStyle
>(
228 columnLinesList
->ElementAt(columnIndex
- 1)));
230 aStyleBorder
.SetBorderStyle(
231 eSideLeft
, static_cast<StyleBorderStyle
>(
232 columnLinesList
->ElementAt(listLength
- 1)));
234 aStyleBorder
.SetBorderWidth(eSideLeft
, borderWidth
, a2d
);
238 static nsMargin
ComputeBorderOverflow(nsMathMLmtdFrame
* aFrame
,
239 const nsStyleBorder
& aStyleBorder
) {
243 nsTableFrame
* table
= aFrame
->GetTableFrame();
244 aFrame
->GetCellIndexes(rowIndex
, columnIndex
);
246 overflow
.left
= table
->GetColSpacing(-1);
247 overflow
.right
= table
->GetColSpacing(0) / 2;
248 } else if (columnIndex
== table
->GetColCount() - 1) {
249 overflow
.left
= table
->GetColSpacing(columnIndex
- 1) / 2;
250 overflow
.right
= table
->GetColSpacing(columnIndex
+ 1);
252 overflow
.left
= table
->GetColSpacing(columnIndex
- 1) / 2;
253 overflow
.right
= table
->GetColSpacing(columnIndex
) / 2;
256 overflow
.top
= table
->GetRowSpacing(-1);
257 overflow
.bottom
= table
->GetRowSpacing(0) / 2;
258 } else if (rowIndex
== table
->GetRowCount() - 1) {
259 overflow
.top
= table
->GetRowSpacing(rowIndex
- 1) / 2;
260 overflow
.bottom
= table
->GetRowSpacing(rowIndex
+ 1);
262 overflow
.top
= table
->GetRowSpacing(rowIndex
- 1) / 2;
263 overflow
.bottom
= table
->GetRowSpacing(rowIndex
) / 2;
269 * A variant of the nsDisplayBorder contains special code to render a border
270 * around a nsMathMLmtdFrame based on the rowline and columnline properties
271 * set on the cell frame.
273 class nsDisplaymtdBorder final
: public nsDisplayBorder
{
275 nsDisplaymtdBorder(nsDisplayListBuilder
* aBuilder
, nsMathMLmtdFrame
* aFrame
)
276 : nsDisplayBorder(aBuilder
, aFrame
) {}
278 nsRect
GetBounds(nsDisplayListBuilder
* aBuilder
, bool* aSnap
) const override
{
280 nsStyleBorder styleBorder
= *mFrame
->StyleBorder();
281 nsMathMLmtdFrame
* frame
= static_cast<nsMathMLmtdFrame
*>(mFrame
);
282 ApplyBorderToStyle(frame
, styleBorder
);
283 nsRect bounds
= CalculateBounds
<nsRect
>(styleBorder
);
284 nsMargin overflow
= ComputeBorderOverflow(frame
, styleBorder
);
285 bounds
.Inflate(overflow
);
289 void Paint(nsDisplayListBuilder
* aBuilder
, gfxContext
* aCtx
) override
{
290 nsStyleBorder styleBorder
= *mFrame
->StyleBorder();
291 nsMathMLmtdFrame
* frame
= static_cast<nsMathMLmtdFrame
*>(mFrame
);
292 ApplyBorderToStyle(frame
, styleBorder
);
294 nsRect bounds
= nsRect(ToReferenceFrame(), mFrame
->GetSize());
295 nsMargin overflow
= ComputeBorderOverflow(frame
, styleBorder
);
296 bounds
.Inflate(overflow
);
298 PaintBorderFlags flags
= aBuilder
->ShouldSyncDecodeImages()
299 ? PaintBorderFlags::SyncDecodeImages
300 : PaintBorderFlags();
302 Unused
<< nsCSSRendering::PaintBorderWithStyleBorder(
303 mFrame
->PresContext(), *aCtx
, mFrame
, GetPaintRect(aBuilder
, aCtx
),
304 bounds
, styleBorder
, mFrame
->Style(), flags
, mFrame
->GetSkipSides());
307 bool CreateWebRenderCommands(
308 mozilla::wr::DisplayListBuilder
& aBuilder
,
309 mozilla::wr::IpcResourceUpdateQueue
& aResources
,
310 const StackingContextHelper
& aSc
,
311 mozilla::layers::RenderRootStateManager
* aManager
,
312 nsDisplayListBuilder
* aDisplayListBuilder
) override
{
316 bool IsInvisibleInRect(const nsRect
& aRect
) const override
{ return false; }
320 # define DEBUG_VERIFY_THAT_FRAME_IS(_frame, _expected) \
322 mozilla::StyleDisplay::_expected == _frame->StyleDisplay()->mDisplay, \
325 # define DEBUG_VERIFY_THAT_FRAME_IS(_frame, _expected)
328 static void ParseFrameAttribute(nsIFrame
* aFrame
, nsAtom
* aAttribute
,
329 bool aAllowMultiValues
) {
330 nsAutoString attrValue
;
332 Element
* frameElement
= aFrame
->GetContent()->AsElement();
333 frameElement
->GetAttr(aAttribute
, attrValue
);
335 if (!attrValue
.IsEmpty()) {
336 nsTArray
<int8_t>* valueList
=
337 ExtractStyleValues(attrValue
, aAttribute
, aAllowMultiValues
);
339 // If valueList is null, that indicates a problem with the attribute value.
340 // Only set properties on a valid attribute value.
342 // The code reading the property assumes that this list is nonempty.
343 NS_ASSERTION(valueList
->Length() >= 1, "valueList should not be empty!");
344 aFrame
->SetProperty(AttributeToProperty(aAttribute
), valueList
);
346 ReportParseError(aFrame
, aAttribute
->GetUTF16String(), attrValue
.get());
353 // Specifies the distance between successive rows in an mtable. Multiple
354 // lengths can be specified, each corresponding to its respective position
355 // between rows. For example:
363 // If the number of row gaps exceeds the number of lengths specified, the final
364 // specified length is repeated. Additional lengths are ignored.
369 // Unitless values are permitted and provide a multiple of the default value
370 // Negative values are forbidden.
375 // Specifies the distance between successive columns in an mtable. Multiple
376 // lengths can be specified, each corresponding to its respective position
377 // between columns. For example:
379 // [COLUMN_0] columnspace_0 [COLUMN_1] columnspace_1 [COLUMN_2]
381 // If the number of column gaps exceeds the number of lengths specified, the
382 // final specified length is repeated. Additional lengths are ignored.
387 // Unitless values are permitted and provide a multiple of the default value
388 // Negative values are forbidden.
393 // Specifies the distance between the mtable and its frame (if any). The
394 // first value specified provides the spacing between the left and right edge
395 // of the table and the frame, the second value determines the spacing between
396 // the top and bottom edges and the frame.
398 // An error is reported if only one length is passed. Any additional lengths
401 // values: length length
402 // default: 0em 0ex If frame attribute is "none" or not specified,
403 // 0.4em 0.5ex otherwise
405 // Unitless values are permitted and provide a multiple of the default value
406 // Negative values are forbidden.
409 static const float kDefaultRowspacingEx
= 1.0f
;
410 static const float kDefaultColumnspacingEm
= 0.8f
;
411 static const float kDefaultFramespacingArg0Em
= 0.4f
;
412 static const float kDefaultFramespacingArg1Ex
= 0.5f
;
414 static void ExtractSpacingValues(const nsAString
& aString
, nsAtom
* aAttribute
,
415 nsTArray
<nscoord
>& aSpacingArray
,
416 nsIFrame
* aFrame
, nscoord aDefaultValue0
,
417 nscoord aDefaultValue1
,
418 float aFontSizeInflation
) {
419 nsPresContext
* presContext
= aFrame
->PresContext();
420 ComputedStyle
* computedStyle
= aFrame
->Style();
422 const char16_t
* start
= aString
.BeginReading();
423 const char16_t
* end
= aString
.EndReading();
425 int32_t startIndex
= 0;
427 int32_t elementNum
= 0;
429 while (start
< end
) {
430 // Skip leading spaces.
431 while ((start
< end
) && nsCRT::IsAsciiSpace(*start
)) {
436 // Look for the end of the string, or another space.
437 while ((start
< end
) && !nsCRT::IsAsciiSpace(*start
)) {
442 // Grab the value found and process it.
444 const nsAString
& str
= Substring(aString
, startIndex
, count
);
445 nsAutoString valueString
;
446 valueString
.Assign(str
);
448 if (aAttribute
== nsGkAtoms::framespacing_
&& elementNum
) {
449 newValue
= aDefaultValue1
;
451 newValue
= aDefaultValue0
;
453 nsMathMLFrame::ParseNumericValue(valueString
, &newValue
, 0, presContext
,
454 computedStyle
, aFontSizeInflation
);
455 aSpacingArray
.AppendElement(newValue
);
464 static void ParseSpacingAttribute(nsMathMLmtableFrame
* aFrame
,
465 nsAtom
* aAttribute
) {
466 NS_ASSERTION(aAttribute
== nsGkAtoms::rowspacing_
||
467 aAttribute
== nsGkAtoms::columnspacing_
||
468 aAttribute
== nsGkAtoms::framespacing_
,
469 "Non spacing attribute passed");
471 nsAutoString attrValue
;
472 Element
* frameElement
= aFrame
->GetContent()->AsElement();
473 frameElement
->GetAttr(aAttribute
, attrValue
);
475 if (nsGkAtoms::framespacing_
== aAttribute
) {
477 frameElement
->GetAttr(nsGkAtoms::frame
, frame
);
478 if (frame
.IsEmpty() || frame
.EqualsLiteral("none")) {
479 aFrame
->SetFrameSpacing(0, 0);
487 float fontSizeInflation
= nsLayoutUtils::FontSizeInflationFor(aFrame
);
488 RefPtr
<nsFontMetrics
> fm
=
489 nsLayoutUtils::GetFontMetricsForFrame(aFrame
, fontSizeInflation
);
490 if (nsGkAtoms::rowspacing_
== aAttribute
) {
491 value
= kDefaultRowspacingEx
* fm
->XHeight();
493 } else if (nsGkAtoms::columnspacing_
== aAttribute
) {
494 value
= kDefaultColumnspacingEm
* fm
->EmHeight();
497 value
= kDefaultFramespacingArg0Em
* fm
->EmHeight();
498 value2
= kDefaultFramespacingArg1Ex
* fm
->XHeight();
501 nsTArray
<nscoord
> valueList
;
502 ExtractSpacingValues(attrValue
, aAttribute
, valueList
, aFrame
, value
, value2
,
504 if (valueList
.Length() == 0) {
505 if (frameElement
->HasAttr(aAttribute
)) {
506 ReportParseError(aFrame
, aAttribute
->GetUTF16String(), attrValue
.get());
508 valueList
.AppendElement(value
);
510 if (aAttribute
== nsGkAtoms::framespacing_
) {
511 if (valueList
.Length() == 1) {
512 if (frameElement
->HasAttr(aAttribute
)) {
513 ReportParseError(aFrame
, aAttribute
->GetUTF16String(), attrValue
.get());
515 valueList
.AppendElement(value2
);
516 } else if (valueList
.Length() != 2) {
517 ReportParseError(aFrame
, aAttribute
->GetUTF16String(), attrValue
.get());
521 if (aAttribute
== nsGkAtoms::rowspacing_
) {
522 aFrame
->SetRowSpacingArray(valueList
);
523 } else if (aAttribute
== nsGkAtoms::columnspacing_
) {
524 aFrame
->SetColSpacingArray(valueList
);
526 aFrame
->SetFrameSpacing(valueList
.ElementAt(0), valueList
.ElementAt(1));
530 static void ParseSpacingAttributes(nsMathMLmtableFrame
* aTableFrame
) {
531 ParseSpacingAttribute(aTableFrame
, nsGkAtoms::rowspacing_
);
532 ParseSpacingAttribute(aTableFrame
, nsGkAtoms::columnspacing_
);
533 ParseSpacingAttribute(aTableFrame
, nsGkAtoms::framespacing_
);
534 aTableFrame
->SetUseCSSSpacing();
537 // map all attributes within a table -- requires the indices of rows and cells.
538 // so it can only happen after they are made ready by the table base class.
539 static void MapAllAttributesIntoCSS(nsMathMLmtableFrame
* aTableFrame
) {
540 // Map mtable rowalign & rowlines.
541 ParseFrameAttribute(aTableFrame
, nsGkAtoms::rowalign_
, true);
542 ParseFrameAttribute(aTableFrame
, nsGkAtoms::rowlines_
, true);
544 // Map mtable columnalign & columnlines.
545 ParseFrameAttribute(aTableFrame
, nsGkAtoms::columnalign_
, true);
546 ParseFrameAttribute(aTableFrame
, nsGkAtoms::columnlines_
, true);
548 // Map mtable rowspacing, columnspacing & framespacing
549 ParseSpacingAttributes(aTableFrame
);
551 // mtable is simple and only has one (pseudo) row-group
552 nsIFrame
* rgFrame
= aTableFrame
->PrincipalChildList().FirstChild();
553 if (!rgFrame
|| !rgFrame
->IsTableRowGroupFrame()) {
557 for (nsIFrame
* rowFrame
: rgFrame
->PrincipalChildList()) {
558 DEBUG_VERIFY_THAT_FRAME_IS(rowFrame
, TableRow
);
559 if (rowFrame
->IsTableRowFrame()) {
561 ParseFrameAttribute(rowFrame
, nsGkAtoms::rowalign_
, false);
562 // Map row columnalign.
563 ParseFrameAttribute(rowFrame
, nsGkAtoms::columnalign_
, true);
565 for (nsIFrame
* cellFrame
: rowFrame
->PrincipalChildList()) {
566 DEBUG_VERIFY_THAT_FRAME_IS(cellFrame
, TableCell
);
567 if (cellFrame
->IsTableCellFrame()) {
568 // Map cell rowalign.
569 ParseFrameAttribute(cellFrame
, nsGkAtoms::rowalign_
, false);
570 // Map row columnalign.
571 ParseFrameAttribute(cellFrame
, nsGkAtoms::columnalign_
, false);
578 // the align attribute of mtable can have a row number which indicates
579 // from where to anchor the table, e.g., top 5 means anchor the table at
580 // the top of the 5th row, axis -1 means anchor the table on the axis of
583 // The REC says that the syntax is
584 // '\s*(top|bottom|center|baseline|axis)(\s+-?[0-9]+)?\s*'
585 // the parsing could have been simpler with that syntax
586 // but for backward compatibility we make optional
587 // the whitespaces between the alignment name and the row number
597 static void ParseAlignAttribute(nsString
& aValue
, eAlign
& aAlign
,
598 int32_t& aRowIndex
) {
599 // by default, the table is centered about the axis
601 aAlign
= eAlign_axis
;
604 // we only have to remove the leading spaces because
605 // ToInteger ignores the whitespaces around the number
606 aValue
.CompressWhitespace(true, false);
608 if (0 == aValue
.Find(u
"top")) {
609 len
= 3; // 3 is the length of 'top'
611 } else if (0 == aValue
.Find(u
"bottom")) {
612 len
= 6; // 6 is the length of 'bottom'
613 aAlign
= eAlign_bottom
;
614 } else if (0 == aValue
.Find(u
"center")) {
615 len
= 6; // 6 is the length of 'center'
616 aAlign
= eAlign_center
;
617 } else if (0 == aValue
.Find(u
"baseline")) {
618 len
= 8; // 8 is the length of 'baseline'
619 aAlign
= eAlign_baseline
;
620 } else if (0 == aValue
.Find(u
"axis")) {
621 len
= 4; // 4 is the length of 'axis'
622 aAlign
= eAlign_axis
;
626 aValue
.Cut(0, len
); // aValue is not a const here
627 aRowIndex
= aValue
.ToInteger(&error
);
628 if (NS_FAILED(error
)) {
635 // implementation of nsMathMLmtableWrapperFrame
637 NS_QUERYFRAME_HEAD(nsMathMLmtableWrapperFrame
)
638 NS_QUERYFRAME_ENTRY(nsIMathMLFrame
)
639 NS_QUERYFRAME_TAIL_INHERITING(nsTableWrapperFrame
)
641 nsContainerFrame
* NS_NewMathMLmtableOuterFrame(PresShell
* aPresShell
,
642 ComputedStyle
* aStyle
) {
643 return new (aPresShell
)
644 nsMathMLmtableWrapperFrame(aStyle
, aPresShell
->GetPresContext());
647 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmtableWrapperFrame
)
649 nsMathMLmtableWrapperFrame::~nsMathMLmtableWrapperFrame() = default;
651 nsresult
nsMathMLmtableWrapperFrame::AttributeChanged(int32_t aNameSpaceID
,
654 // Attributes specific to <mtable>:
655 // frame : in mathml.css
656 // framespacing : here
657 // groupalign : not yet supported
658 // equalrows : not yet supported
659 // equalcolumns : not yet supported
660 // displaystyle : here and in mathml.css
665 // columnalign : here
666 // columnlines : here
667 // columnspacing : here
669 // mtable is simple and only has one (pseudo) row-group inside our inner-table
670 nsIFrame
* tableFrame
= mFrames
.FirstChild();
671 NS_ASSERTION(tableFrame
&& tableFrame
->IsTableFrame(),
672 "should always have an inner table frame");
673 nsIFrame
* rgFrame
= tableFrame
->PrincipalChildList().FirstChild();
674 if (!rgFrame
|| !rgFrame
->IsTableRowGroupFrame()) {
678 // align - just need to issue a dirty (resize) reflow command
679 if (aNameSpaceID
== kNameSpaceID_None
&& aAttribute
== nsGkAtoms::align
) {
680 PresShell()->FrameNeedsReflow(this, IntrinsicDirty::None
,
685 // ...and the other attributes affect rows or columns in one way or another
687 if (aNameSpaceID
== kNameSpaceID_None
&&
688 (aAttribute
== nsGkAtoms::rowspacing_
||
689 aAttribute
== nsGkAtoms::columnspacing_
||
690 aAttribute
== nsGkAtoms::framespacing_
)) {
691 nsMathMLmtableFrame
* mathMLmtableFrame
= do_QueryFrame(tableFrame
);
692 if (mathMLmtableFrame
) {
693 ParseSpacingAttribute(mathMLmtableFrame
, aAttribute
);
694 mathMLmtableFrame
->SetUseCSSSpacing();
696 PresShell()->FrameNeedsReflow(
697 this, IntrinsicDirty::FrameAncestorsAndDescendants
, NS_FRAME_IS_DIRTY
);
701 if (aNameSpaceID
== kNameSpaceID_None
&&
702 (aAttribute
== nsGkAtoms::rowalign_
||
703 aAttribute
== nsGkAtoms::rowlines_
||
704 aAttribute
== nsGkAtoms::columnalign_
||
705 aAttribute
== nsGkAtoms::columnlines_
)) {
706 // clear any cached property list for this table
707 tableFrame
->RemoveProperty(AttributeToProperty(aAttribute
));
708 // Reparse the new attribute on the table.
709 ParseFrameAttribute(tableFrame
, aAttribute
, true);
710 PresShell()->FrameNeedsReflow(
711 this, IntrinsicDirty::FrameAncestorsAndDescendants
, NS_FRAME_IS_DIRTY
);
715 // Skip nsTableWrapperFrame::AttributeChanged, mtable does not share more
716 // attributes with table.
717 return nsContainerFrame::AttributeChanged(aNameSpaceID
, aAttribute
, aModType
);
720 nsIFrame
* nsMathMLmtableWrapperFrame::GetRowFrameAt(int32_t aRowIndex
) {
721 int32_t rowCount
= GetRowCount();
723 // Negative indices mean to find upwards from the end.
725 aRowIndex
= rowCount
+ aRowIndex
;
727 // aRowIndex is 1-based, so convert it to a 0-based index
731 // if our inner table says that the index is valid, find the row now
732 if (0 <= aRowIndex
&& aRowIndex
<= rowCount
) {
733 nsIFrame
* tableFrame
= mFrames
.FirstChild();
734 NS_ASSERTION(tableFrame
&& tableFrame
->IsTableFrame(),
735 "should always have an inner table frame");
736 nsIFrame
* rgFrame
= tableFrame
->PrincipalChildList().FirstChild();
737 if (!rgFrame
|| !rgFrame
->IsTableRowGroupFrame()) {
740 for (nsIFrame
* rowFrame
: rgFrame
->PrincipalChildList()) {
741 if (aRowIndex
== 0) {
742 DEBUG_VERIFY_THAT_FRAME_IS(rowFrame
, TableRow
);
743 if (!rowFrame
->IsTableRowFrame()) {
755 void nsMathMLmtableWrapperFrame::Reflow(nsPresContext
* aPresContext
,
756 ReflowOutput
& aDesiredSize
,
757 const ReflowInput
& aReflowInput
,
758 nsReflowStatus
& aStatus
) {
759 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
762 // we want to return a table that is anchored according to the align attribute
764 nsTableWrapperFrame::Reflow(aPresContext
, aDesiredSize
, aReflowInput
,
766 NS_ASSERTION(aDesiredSize
.Height() >= 0, "illegal height for mtable");
767 NS_ASSERTION(aDesiredSize
.Width() >= 0, "illegal width for mtable");
769 // see if the user has set the align attribute on the <mtable>
770 int32_t rowIndex
= 0;
771 eAlign tableAlign
= eAlign_axis
;
772 mContent
->AsElement()->GetAttr(nsGkAtoms::align
, value
);
773 if (!value
.IsEmpty()) {
774 ParseAlignAttribute(value
, tableAlign
, rowIndex
);
777 // adjustments if there is a specified row from where to anchor the table
778 // (conceptually: when there is no row of reference, picture the table as if
779 // it is wrapped in a single big fictional row at dy = 0, this way of
780 // doing so allows us to have a single code path for all cases).
782 WritingMode wm
= aDesiredSize
.GetWritingMode();
783 nscoord blockSize
= aDesiredSize
.BSize(wm
);
784 nsIFrame
* rowFrame
= nullptr;
786 rowFrame
= GetRowFrameAt(rowIndex
);
788 // translate the coordinates to be relative to us and in our writing mode
789 nsIFrame
* frame
= rowFrame
;
790 LogicalRect
rect(wm
, frame
->GetRect(),
791 aReflowInput
.ComputedSizeAsContainerIfConstrained());
792 blockSize
= rect
.BSize(wm
);
794 nsIFrame
* parent
= frame
->GetParent();
795 dy
+= frame
->BStart(wm
, parent
->GetSize());
797 } while (frame
!= this);
800 switch (tableAlign
) {
802 aDesiredSize
.SetBlockStartAscent(dy
);
805 aDesiredSize
.SetBlockStartAscent(dy
+ blockSize
);
808 aDesiredSize
.SetBlockStartAscent(dy
+ blockSize
/ 2);
810 case eAlign_baseline
:
812 // anchor the table on the baseline of the row of reference
813 nscoord rowAscent
= ((nsTableRowFrame
*)rowFrame
)->GetMaxCellAscent();
814 if (rowAscent
) { // the row has at least one cell with 'vertical-align:
816 aDesiredSize
.SetBlockStartAscent(dy
+ rowAscent
);
820 // in other situations, fallback to center
821 aDesiredSize
.SetBlockStartAscent(dy
+ blockSize
/ 2);
825 // XXX should instead use style data from the row of reference here ?
826 RefPtr
<nsFontMetrics
> fm
=
827 nsLayoutUtils::GetInflatedFontMetricsForFrame(this);
829 GetAxisHeight(aReflowInput
.mRenderingContext
->GetDrawTarget(), fm
,
832 // anchor the table on the axis of the row of reference
833 // XXX fallback to baseline because it is a hard problem
834 // XXX need to fetch the axis of the row; would need rowalign=axis to
836 nscoord rowAscent
= ((nsTableRowFrame
*)rowFrame
)->GetMaxCellAscent();
837 if (rowAscent
) { // the row has at least one cell with 'vertical-align:
839 aDesiredSize
.SetBlockStartAscent(dy
+ rowAscent
);
843 // in other situations, fallback to using half of the height
844 aDesiredSize
.SetBlockStartAscent(dy
+ blockSize
/ 2 + axisHeight
);
849 mReference
.y
= aDesiredSize
.BlockStartAscent();
851 // just make-up a bounding metrics
852 mBoundingMetrics
= nsBoundingMetrics();
853 mBoundingMetrics
.ascent
= aDesiredSize
.BlockStartAscent();
854 mBoundingMetrics
.descent
=
855 aDesiredSize
.Height() - aDesiredSize
.BlockStartAscent();
856 mBoundingMetrics
.width
= aDesiredSize
.Width();
857 mBoundingMetrics
.leftBearing
= 0;
858 mBoundingMetrics
.rightBearing
= aDesiredSize
.Width();
860 aDesiredSize
.mBoundingMetrics
= mBoundingMetrics
;
863 nsContainerFrame
* NS_NewMathMLmtableFrame(PresShell
* aPresShell
,
864 ComputedStyle
* aStyle
) {
865 return new (aPresShell
)
866 nsMathMLmtableFrame(aStyle
, aPresShell
->GetPresContext());
869 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmtableFrame
)
871 nsMathMLmtableFrame::~nsMathMLmtableFrame() = default;
873 void nsMathMLmtableFrame::SetInitialChildList(ChildListID aListID
,
874 nsFrameList
&& aChildList
) {
875 nsTableFrame::SetInitialChildList(aListID
, std::move(aChildList
));
876 MapAllAttributesIntoCSS(this);
879 void nsMathMLmtableFrame::RestyleTable() {
880 // re-sync MathML specific style data that may have changed
881 MapAllAttributesIntoCSS(this);
883 // Explicitly request a re-resolve and reflow in our subtree to pick up any
885 PresContext()->RestyleManager()->PostRestyleEvent(
886 mContent
->AsElement(), RestyleHint::RestyleSubtree(),
887 nsChangeHint_AllReflowHints
);
890 nscoord
nsMathMLmtableFrame::GetColSpacing(int32_t aColIndex
) {
891 if (mUseCSSSpacing
) {
892 return nsTableFrame::GetColSpacing(aColIndex
);
894 if (!mColSpacing
.Length()) {
895 NS_ERROR("mColSpacing should not be empty");
898 if (aColIndex
< 0 || aColIndex
>= GetColCount()) {
899 NS_ASSERTION(aColIndex
== -1 || aColIndex
== GetColCount(),
900 "Desired column beyond bounds of table and border");
901 return mFrameSpacingX
;
903 if ((uint32_t)aColIndex
>= mColSpacing
.Length()) {
904 return mColSpacing
.LastElement();
906 return mColSpacing
.ElementAt(aColIndex
);
909 nscoord
nsMathMLmtableFrame::GetColSpacing(int32_t aStartColIndex
,
910 int32_t aEndColIndex
) {
911 if (mUseCSSSpacing
) {
912 return nsTableFrame::GetColSpacing(aStartColIndex
, aEndColIndex
);
914 if (aStartColIndex
== aEndColIndex
) {
917 if (!mColSpacing
.Length()) {
918 NS_ERROR("mColSpacing should not be empty");
922 if (aStartColIndex
< 0) {
923 NS_ASSERTION(aStartColIndex
== -1,
924 "Desired column beyond bounds of table and border");
925 space
+= mFrameSpacingX
;
928 if (aEndColIndex
>= GetColCount()) {
929 NS_ASSERTION(aEndColIndex
== GetColCount(),
930 "Desired column beyond bounds of table and border");
931 space
+= mFrameSpacingX
;
932 aEndColIndex
= GetColCount();
934 // Only iterate over column spacing when there is the potential to vary
935 int32_t min
= std::min(aEndColIndex
, (int32_t)mColSpacing
.Length());
936 for (int32_t i
= aStartColIndex
; i
< min
; i
++) {
937 space
+= mColSpacing
.ElementAt(i
);
939 // The remaining values are constant. Note that if there are more
940 // column spacings specified than there are columns, LastElement() will be
941 // multiplied by 0, so it is still safe to use.
942 space
+= (aEndColIndex
- min
) * mColSpacing
.LastElement();
946 nscoord
nsMathMLmtableFrame::GetRowSpacing(int32_t aRowIndex
) {
947 if (mUseCSSSpacing
) {
948 return nsTableFrame::GetRowSpacing(aRowIndex
);
950 if (!mRowSpacing
.Length()) {
951 NS_ERROR("mRowSpacing should not be empty");
954 if (aRowIndex
< 0 || aRowIndex
>= GetRowCount()) {
955 NS_ASSERTION(aRowIndex
== -1 || aRowIndex
== GetRowCount(),
956 "Desired row beyond bounds of table and border");
957 return mFrameSpacingY
;
959 if ((uint32_t)aRowIndex
>= mRowSpacing
.Length()) {
960 return mRowSpacing
.LastElement();
962 return mRowSpacing
.ElementAt(aRowIndex
);
965 nscoord
nsMathMLmtableFrame::GetRowSpacing(int32_t aStartRowIndex
,
966 int32_t aEndRowIndex
) {
967 if (mUseCSSSpacing
) {
968 return nsTableFrame::GetRowSpacing(aStartRowIndex
, aEndRowIndex
);
970 if (aStartRowIndex
== aEndRowIndex
) {
973 if (!mRowSpacing
.Length()) {
974 NS_ERROR("mRowSpacing should not be empty");
978 if (aStartRowIndex
< 0) {
979 NS_ASSERTION(aStartRowIndex
== -1,
980 "Desired row beyond bounds of table and border");
981 space
+= mFrameSpacingY
;
984 if (aEndRowIndex
>= GetRowCount()) {
985 NS_ASSERTION(aEndRowIndex
== GetRowCount(),
986 "Desired row beyond bounds of table and border");
987 space
+= mFrameSpacingY
;
988 aEndRowIndex
= GetRowCount();
990 // Only iterate over row spacing when there is the potential to vary
991 int32_t min
= std::min(aEndRowIndex
, (int32_t)mRowSpacing
.Length());
992 for (int32_t i
= aStartRowIndex
; i
< min
; i
++) {
993 space
+= mRowSpacing
.ElementAt(i
);
995 // The remaining values are constant. Note that if there are more
996 // row spacings specified than there are row, LastElement() will be
997 // multiplied by 0, so it is still safe to use.
998 space
+= (aEndRowIndex
- min
) * mRowSpacing
.LastElement();
1002 void nsMathMLmtableFrame::SetUseCSSSpacing() {
1003 mUseCSSSpacing
= !(mContent
->AsElement()->HasAttr(nsGkAtoms::rowspacing_
) ||
1004 mContent
->AsElement()->HasAttr(
1005 kNameSpaceID_None
, nsGkAtoms::columnspacing_
) ||
1006 mContent
->AsElement()->HasAttr(nsGkAtoms::framespacing_
));
1009 NS_QUERYFRAME_HEAD(nsMathMLmtableFrame
)
1010 NS_QUERYFRAME_ENTRY(nsMathMLmtableFrame
)
1011 NS_QUERYFRAME_TAIL_INHERITING(nsTableFrame
)
1014 // implementation of nsMathMLmtrFrame
1016 nsContainerFrame
* NS_NewMathMLmtrFrame(PresShell
* aPresShell
,
1017 ComputedStyle
* aStyle
) {
1018 return new (aPresShell
)
1019 nsMathMLmtrFrame(aStyle
, aPresShell
->GetPresContext());
1022 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmtrFrame
)
1024 nsMathMLmtrFrame::~nsMathMLmtrFrame() = default;
1026 nsresult
nsMathMLmtrFrame::AttributeChanged(int32_t aNameSpaceID
,
1029 // Attributes specific to <mtr>:
1030 // groupalign : Not yet supported.
1032 // columnalign : Here
1034 if (aNameSpaceID
!= kNameSpaceID_None
||
1035 (aAttribute
!= nsGkAtoms::rowalign_
&&
1036 aAttribute
!= nsGkAtoms::columnalign_
)) {
1037 // Skip nsTableCellFrame::AttributeChanged, mtr does not share any attribute
1039 return nsContainerFrame::AttributeChanged(aNameSpaceID
, aAttribute
,
1043 RemoveProperty(AttributeToProperty(aAttribute
));
1045 bool allowMultiValues
= (aAttribute
== nsGkAtoms::columnalign_
);
1047 // Reparse the new attribute.
1048 ParseFrameAttribute(this, aAttribute
, allowMultiValues
);
1050 // Explicitly request a reflow in our subtree to pick up any changes
1051 PresShell()->FrameNeedsReflow(
1052 this, IntrinsicDirty::FrameAncestorsAndDescendants
, NS_FRAME_IS_DIRTY
);
1058 // implementation of nsMathMLmtdFrame
1060 nsContainerFrame
* NS_NewMathMLmtdFrame(PresShell
* aPresShell
,
1061 ComputedStyle
* aStyle
,
1062 nsTableFrame
* aTableFrame
) {
1063 return new (aPresShell
) nsMathMLmtdFrame(aStyle
, aTableFrame
);
1066 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmtdFrame
)
1068 nsMathMLmtdFrame::~nsMathMLmtdFrame() = default;
1070 void nsMathMLmtdFrame::Init(nsIContent
* aContent
, nsContainerFrame
* aParent
,
1071 nsIFrame
* aPrevInFlow
) {
1072 nsTableCellFrame::Init(aContent
, aParent
, aPrevInFlow
);
1074 // We want to use the ancestor <math> element's font inflation to avoid
1075 // individual cells having their own varying font inflation.
1076 RemoveStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT
);
1079 nsresult
nsMathMLmtdFrame::AttributeChanged(int32_t aNameSpaceID
,
1082 // Attributes specific to <mtd>:
1083 // groupalign : Not yet supported
1085 // columnalign : here
1087 // columnspan : here
1089 if (aNameSpaceID
== kNameSpaceID_None
&&
1090 (aAttribute
== nsGkAtoms::rowalign_
||
1091 aAttribute
== nsGkAtoms::columnalign_
)) {
1092 RemoveProperty(AttributeToProperty(aAttribute
));
1094 // Reparse the attribute.
1095 ParseFrameAttribute(this, aAttribute
, false);
1099 if (aNameSpaceID
== kNameSpaceID_None
&&
1100 (aAttribute
== nsGkAtoms::rowspan
||
1101 aAttribute
== nsGkAtoms::columnspan_
)) {
1102 // nsTableCellFrame takes care of renaming columnspan to colspan.
1103 return nsTableCellFrame::AttributeChanged(aNameSpaceID
, aAttribute
,
1107 // Skip nsTableCellFrame::AttributeChanged, mtd does not share more attributes
1109 return nsContainerFrame::AttributeChanged(aNameSpaceID
, aAttribute
, aModType
);
1112 StyleVerticalAlignKeyword
nsMathMLmtdFrame::GetVerticalAlign() const {
1113 // Set the default alignment in case no alignment was specified
1114 auto alignment
= nsTableCellFrame::GetVerticalAlign();
1116 nsTArray
<int8_t>* alignmentList
= FindCellProperty(this, RowAlignProperty());
1118 if (alignmentList
) {
1119 uint32_t rowIndex
= RowIndex();
1121 // If the row number is greater than the number of provided rowalign values,
1122 // we simply repeat the last value.
1123 return static_cast<StyleVerticalAlignKeyword
>(
1124 (rowIndex
< alignmentList
->Length())
1125 ? alignmentList
->ElementAt(rowIndex
)
1126 : alignmentList
->LastElement());
1132 void nsMathMLmtdFrame::ProcessBorders(nsTableFrame
* aFrame
,
1133 nsDisplayListBuilder
* aBuilder
,
1134 const nsDisplayListSet
& aLists
) {
1135 aLists
.BorderBackground()->AppendNewToTop
<nsDisplaymtdBorder
>(aBuilder
, this);
1138 LogicalMargin
nsMathMLmtdFrame::GetBorderWidth(WritingMode aWM
) const {
1139 nsStyleBorder styleBorder
= *StyleBorder();
1140 ApplyBorderToStyle(this, styleBorder
);
1141 return LogicalMargin(aWM
, styleBorder
.GetComputedBorder());
1144 nsMargin
nsMathMLmtdFrame::GetBorderOverflow() {
1145 nsStyleBorder styleBorder
= *StyleBorder();
1146 ApplyBorderToStyle(this, styleBorder
);
1147 nsMargin overflow
= ComputeBorderOverflow(this, styleBorder
);
1152 // implementation of nsMathMLmtdInnerFrame
1154 NS_QUERYFRAME_HEAD(nsMathMLmtdInnerFrame
)
1155 NS_QUERYFRAME_ENTRY(nsIMathMLFrame
)
1156 NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame
)
1158 nsContainerFrame
* NS_NewMathMLmtdInnerFrame(PresShell
* aPresShell
,
1159 ComputedStyle
* aStyle
) {
1160 return new (aPresShell
)
1161 nsMathMLmtdInnerFrame(aStyle
, aPresShell
->GetPresContext());
1164 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmtdInnerFrame
)
1166 nsMathMLmtdInnerFrame::nsMathMLmtdInnerFrame(ComputedStyle
* aStyle
,
1167 nsPresContext
* aPresContext
)
1168 : nsBlockFrame(aStyle
, aPresContext
, kClassID
)
1169 // Make a copy of the parent nsStyleText for later modification.
1171 mUniqueStyleText(MakeUnique
<nsStyleText
>(*StyleText())) {}
1173 void nsMathMLmtdInnerFrame::Reflow(nsPresContext
* aPresContext
,
1174 ReflowOutput
& aDesiredSize
,
1175 const ReflowInput
& aReflowInput
,
1176 nsReflowStatus
& aStatus
) {
1177 // Let the base class do the reflow
1178 nsBlockFrame::Reflow(aPresContext
, aDesiredSize
, aReflowInput
, aStatus
);
1180 // more about <maligngroup/> and <malignmark/> later
1184 const nsStyleText
* nsMathMLmtdInnerFrame::StyleTextForLineLayout() {
1185 // Set the default alignment in case nothing was specified
1186 auto alignment
= uint8_t(StyleText()->mTextAlign
);
1188 nsTArray
<int8_t>* alignmentList
=
1189 FindCellProperty(this, ColumnAlignProperty());
1191 if (alignmentList
) {
1192 nsMathMLmtdFrame
* cellFrame
= (nsMathMLmtdFrame
*)GetParent();
1193 uint32_t columnIndex
= cellFrame
->ColIndex();
1195 // If the column number is greater than the number of provided columalign
1196 // values, we simply repeat the last value.
1197 if (columnIndex
< alignmentList
->Length()) {
1198 alignment
= alignmentList
->ElementAt(columnIndex
);
1200 alignment
= alignmentList
->ElementAt(alignmentList
->Length() - 1);
1204 mUniqueStyleText
->mTextAlign
= StyleTextAlign(alignment
);
1205 return mUniqueStyleText
.get();
1209 void nsMathMLmtdInnerFrame::DidSetComputedStyle(
1210 ComputedStyle
* aOldComputedStyle
) {
1211 nsBlockFrame::DidSetComputedStyle(aOldComputedStyle
);
1212 mUniqueStyleText
= MakeUnique
<nsStyleText
>(*StyleText());