diff options
Diffstat (limited to 'layout/mathml/nsMathMLmtableFrame.cpp')
-rw-r--r-- | layout/mathml/nsMathMLmtableFrame.cpp | 1215 |
1 files changed, 1215 insertions, 0 deletions
diff --git a/layout/mathml/nsMathMLmtableFrame.cpp b/layout/mathml/nsMathMLmtableFrame.cpp new file mode 100644 index 0000000000..77f132b342 --- /dev/null +++ b/layout/mathml/nsMathMLmtableFrame.cpp @@ -0,0 +1,1215 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gfxContext.h" +#include "nsMathMLmtableFrame.h" +#include "nsPresContext.h" +#include "nsStyleConsts.h" +#include "nsNameSpaceManager.h" +#include "nsCSSRendering.h" +#include "mozilla/dom/MathMLElement.h" + +#include "nsCRT.h" +#include "nsTArray.h" +#include "nsTableFrame.h" +#include "celldata.h" + +#include "mozilla/PresShell.h" +#include "mozilla/RestyleManager.h" +#include <algorithm> + +#include "nsIScriptError.h" +#include "nsContentUtils.h" +#include "nsLayoutUtils.h" + +using namespace mozilla; +using namespace mozilla::image; +using mozilla::dom::Element; + +// +// <mtable> -- table or matrix - implementation +// + +static int8_t ParseStyleValue(nsAtom* aAttribute, + const nsAString& aAttributeValue) { + if (aAttribute == nsGkAtoms::rowalign_) { + if (aAttributeValue.EqualsLiteral("top")) { + return static_cast<int8_t>(StyleVerticalAlignKeyword::Top); + } + if (aAttributeValue.EqualsLiteral("bottom")) { + return static_cast<int8_t>(StyleVerticalAlignKeyword::Bottom); + } + if (aAttributeValue.EqualsLiteral("center")) { + return static_cast<int8_t>(StyleVerticalAlignKeyword::Middle); + } + return static_cast<int8_t>(StyleVerticalAlignKeyword::Baseline); + } + + if (aAttribute == nsGkAtoms::columnalign_) { + if (aAttributeValue.EqualsLiteral("left")) { + return int8_t(StyleTextAlign::Left); + } + if (aAttributeValue.EqualsLiteral("right")) { + return int8_t(StyleTextAlign::Right); + } + return int8_t(StyleTextAlign::Center); + } + + if (aAttribute == nsGkAtoms::rowlines_ || + aAttribute == nsGkAtoms::columnlines_) { + if (aAttributeValue.EqualsLiteral("solid")) { + return static_cast<int8_t>(StyleBorderStyle::Solid); + } + if (aAttributeValue.EqualsLiteral("dashed")) { + return static_cast<int8_t>(StyleBorderStyle::Dashed); + } + return static_cast<int8_t>(StyleBorderStyle::None); + } + + MOZ_CRASH("Unrecognized attribute."); + return -1; +} + +static nsTArray<int8_t>* ExtractStyleValues(const nsAString& aString, + nsAtom* aAttribute, + bool aAllowMultiValues) { + nsTArray<int8_t>* styleArray = nullptr; + + const char16_t* start = aString.BeginReading(); + const char16_t* end = aString.EndReading(); + + int32_t startIndex = 0; + int32_t count = 0; + + while (start < end) { + // Skip leading spaces. + while ((start < end) && nsCRT::IsAsciiSpace(*start)) { + start++; + startIndex++; + } + + // Look for the end of the string, or another space. + while ((start < end) && !nsCRT::IsAsciiSpace(*start)) { + start++; + count++; + } + + // Grab the value found and process it. + if (count > 0) { + if (!styleArray) styleArray = new nsTArray<int8_t>(); + + // We want to return a null array if an attribute gives multiple values, + // but multiple values aren't allowed. + if (styleArray->Length() > 1 && !aAllowMultiValues) { + delete styleArray; + return nullptr; + } + + nsDependentSubstring valueString(aString, startIndex, count); + int8_t styleValue = ParseStyleValue(aAttribute, valueString); + styleArray->AppendElement(styleValue); + + startIndex += count; + count = 0; + } + } + return styleArray; +} + +static nsresult ReportParseError(nsIFrame* aFrame, const char16_t* aAttribute, + const char16_t* aValue) { + nsIContent* content = aFrame->GetContent(); + + AutoTArray<nsString, 3> params; + params.AppendElement(aValue); + params.AppendElement(aAttribute); + params.AppendElement(nsDependentAtomString(content->NodeInfo()->NameAtom())); + + return nsContentUtils::ReportToConsole( + nsIScriptError::errorFlag, "Layout: MathML"_ns, content->OwnerDoc(), + nsContentUtils::eMATHML_PROPERTIES, "AttributeParsingError", params); +} + +// Each rowalign='top bottom' or columnalign='left right center' (from +// <mtable> or <mtr>) is split once into an nsTArray<int8_t> which is +// stored in the property table. Row/Cell frames query the property table +// to see what values apply to them. + +NS_DECLARE_FRAME_PROPERTY_DELETABLE(RowAlignProperty, nsTArray<int8_t>) +NS_DECLARE_FRAME_PROPERTY_DELETABLE(RowLinesProperty, nsTArray<int8_t>) +NS_DECLARE_FRAME_PROPERTY_DELETABLE(ColumnAlignProperty, nsTArray<int8_t>) +NS_DECLARE_FRAME_PROPERTY_DELETABLE(ColumnLinesProperty, nsTArray<int8_t>) + +static const FramePropertyDescriptor<nsTArray<int8_t>>* AttributeToProperty( + nsAtom* aAttribute) { + if (aAttribute == nsGkAtoms::rowalign_) return RowAlignProperty(); + if (aAttribute == nsGkAtoms::rowlines_) return RowLinesProperty(); + if (aAttribute == nsGkAtoms::columnalign_) return ColumnAlignProperty(); + NS_ASSERTION(aAttribute == nsGkAtoms::columnlines_, "Invalid attribute"); + return ColumnLinesProperty(); +} + +/* This method looks for a property that applies to a cell, but it looks + * recursively because some cell properties can come from the cell, a row, + * a table, etc. This function searches through the hierarchy for a property + * and returns its value. The function stops searching after checking a <mtable> + * frame. + */ +static nsTArray<int8_t>* FindCellProperty( + const nsIFrame* aCellFrame, + const FramePropertyDescriptor<nsTArray<int8_t>>* aFrameProperty) { + const nsIFrame* currentFrame = aCellFrame; + nsTArray<int8_t>* propertyData = nullptr; + + while (currentFrame) { + propertyData = currentFrame->GetProperty(aFrameProperty); + bool frameIsTable = (currentFrame->IsTableFrame()); + + if (propertyData || frameIsTable) + currentFrame = nullptr; // A null frame pointer exits the loop + else + currentFrame = currentFrame->GetParent(); // Go to the parent frame + } + + return propertyData; +} + +static void ApplyBorderToStyle(const nsMathMLmtdFrame* aFrame, + nsStyleBorder& aStyleBorder) { + uint32_t rowIndex = aFrame->RowIndex(); + uint32_t columnIndex = aFrame->ColIndex(); + + nscoord borderWidth = nsPresContext::CSSPixelsToAppUnits(1); + + nsTArray<int8_t>* rowLinesList = FindCellProperty(aFrame, RowLinesProperty()); + + nsTArray<int8_t>* columnLinesList = + FindCellProperty(aFrame, ColumnLinesProperty()); + + const auto a2d = aFrame->PresContext()->AppUnitsPerDevPixel(); + + // We don't place a row line on top of the first row + if (rowIndex > 0 && rowLinesList) { + // If the row number is greater than the number of provided rowline + // values, we simply repeat the last value. + uint32_t listLength = rowLinesList->Length(); + if (rowIndex < listLength) { + aStyleBorder.SetBorderStyle( + eSideTop, + static_cast<StyleBorderStyle>(rowLinesList->ElementAt(rowIndex - 1))); + } else { + aStyleBorder.SetBorderStyle(eSideTop, + static_cast<StyleBorderStyle>( + rowLinesList->ElementAt(listLength - 1))); + } + aStyleBorder.SetBorderWidth(eSideTop, borderWidth, a2d); + } + + // We don't place a column line on the left of the first column. + if (columnIndex > 0 && columnLinesList) { + // If the column number is greater than the number of provided columline + // values, we simply repeat the last value. + uint32_t listLength = columnLinesList->Length(); + if (columnIndex < listLength) { + aStyleBorder.SetBorderStyle( + eSideLeft, static_cast<StyleBorderStyle>( + columnLinesList->ElementAt(columnIndex - 1))); + } else { + aStyleBorder.SetBorderStyle( + eSideLeft, static_cast<StyleBorderStyle>( + columnLinesList->ElementAt(listLength - 1))); + } + aStyleBorder.SetBorderWidth(eSideLeft, borderWidth, a2d); + } +} + +static nsMargin ComputeBorderOverflow(nsMathMLmtdFrame* aFrame, + const nsStyleBorder& aStyleBorder) { + nsMargin overflow; + int32_t rowIndex; + int32_t columnIndex; + nsTableFrame* table = aFrame->GetTableFrame(); + aFrame->GetCellIndexes(rowIndex, columnIndex); + if (!columnIndex) { + overflow.left = table->GetColSpacing(-1); + overflow.right = table->GetColSpacing(0) / 2; + } else if (columnIndex == table->GetColCount() - 1) { + overflow.left = table->GetColSpacing(columnIndex - 1) / 2; + overflow.right = table->GetColSpacing(columnIndex + 1); + } else { + overflow.left = table->GetColSpacing(columnIndex - 1) / 2; + overflow.right = table->GetColSpacing(columnIndex) / 2; + } + if (!rowIndex) { + overflow.top = table->GetRowSpacing(-1); + overflow.bottom = table->GetRowSpacing(0) / 2; + } else if (rowIndex == table->GetRowCount() - 1) { + overflow.top = table->GetRowSpacing(rowIndex - 1) / 2; + overflow.bottom = table->GetRowSpacing(rowIndex + 1); + } else { + overflow.top = table->GetRowSpacing(rowIndex - 1) / 2; + overflow.bottom = table->GetRowSpacing(rowIndex) / 2; + } + return overflow; +} + +/* + * A variant of the nsDisplayBorder contains special code to render a border + * around a nsMathMLmtdFrame based on the rowline and columnline properties + * set on the cell frame. + */ +class nsDisplaymtdBorder final : public nsDisplayBorder { + public: + nsDisplaymtdBorder(nsDisplayListBuilder* aBuilder, nsMathMLmtdFrame* aFrame) + : nsDisplayBorder(aBuilder, aFrame) {} + + virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const override { + *aSnap = true; + nsStyleBorder styleBorder = *mFrame->StyleBorder(); + nsMathMLmtdFrame* frame = static_cast<nsMathMLmtdFrame*>(mFrame); + ApplyBorderToStyle(frame, styleBorder); + nsRect bounds = CalculateBounds<nsRect>(styleBorder); + nsMargin overflow = ComputeBorderOverflow(frame, styleBorder); + bounds.Inflate(overflow); + return bounds; + } + + virtual void Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) override { + nsStyleBorder styleBorder = *mFrame->StyleBorder(); + nsMathMLmtdFrame* frame = static_cast<nsMathMLmtdFrame*>(mFrame); + ApplyBorderToStyle(frame, styleBorder); + + nsRect bounds = nsRect(ToReferenceFrame(), mFrame->GetSize()); + nsMargin overflow = ComputeBorderOverflow(frame, styleBorder); + bounds.Inflate(overflow); + + PaintBorderFlags flags = aBuilder->ShouldSyncDecodeImages() + ? PaintBorderFlags::SyncDecodeImages + : PaintBorderFlags(); + + Unused << nsCSSRendering::PaintBorderWithStyleBorder( + mFrame->PresContext(), *aCtx, mFrame, GetPaintRect(aBuilder, aCtx), + bounds, styleBorder, mFrame->Style(), flags, mFrame->GetSkipSides()); + } + + bool CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) override { + return false; + } + + virtual bool IsInvisibleInRect(const nsRect& aRect) const override { + return false; + } +}; + +#ifdef DEBUG +# define DEBUG_VERIFY_THAT_FRAME_IS(_frame, _expected) \ + MOZ_ASSERT( \ + mozilla::StyleDisplay::_expected == _frame->StyleDisplay()->mDisplay, \ + "internal error"); +#else +# define DEBUG_VERIFY_THAT_FRAME_IS(_frame, _expected) +#endif + +static void ParseFrameAttribute(nsIFrame* aFrame, nsAtom* aAttribute, + bool aAllowMultiValues) { + nsAutoString attrValue; + + Element* frameElement = aFrame->GetContent()->AsElement(); + frameElement->GetAttr(aAttribute, attrValue); + + if (!attrValue.IsEmpty()) { + nsTArray<int8_t>* valueList = + ExtractStyleValues(attrValue, aAttribute, aAllowMultiValues); + + // If valueList is null, that indicates a problem with the attribute value. + // Only set properties on a valid attribute value. + if (valueList) { + // The code reading the property assumes that this list is nonempty. + NS_ASSERTION(valueList->Length() >= 1, "valueList should not be empty!"); + aFrame->SetProperty(AttributeToProperty(aAttribute), valueList); + } else { + ReportParseError(aFrame, aAttribute->GetUTF16String(), attrValue.get()); + } + } +} + +// rowspacing +// +// Specifies the distance between successive rows in an mtable. Multiple +// lengths can be specified, each corresponding to its respective position +// between rows. For example: +// +// [ROW_0] +// rowspace_0 +// [ROW_1] +// rowspace_1 +// [ROW_2] +// +// If the number of row gaps exceeds the number of lengths specified, the final +// specified length is repeated. Additional lengths are ignored. +// +// values: (length)+ +// default: 1.0ex +// +// Unitless values are permitted and provide a multiple of the default value +// Negative values are forbidden. +// + +// columnspacing +// +// Specifies the distance between successive columns in an mtable. Multiple +// lengths can be specified, each corresponding to its respective position +// between columns. For example: +// +// [COLUMN_0] columnspace_0 [COLUMN_1] columnspace_1 [COLUMN_2] +// +// If the number of column gaps exceeds the number of lengths specified, the +// final specified length is repeated. Additional lengths are ignored. +// +// values: (length)+ +// default: 0.8em +// +// Unitless values are permitted and provide a multiple of the default value +// Negative values are forbidden. +// + +// framespacing +// +// Specifies the distance between the mtable and its frame (if any). The +// first value specified provides the spacing between the left and right edge +// of the table and the frame, the second value determines the spacing between +// the top and bottom edges and the frame. +// +// An error is reported if only one length is passed. Any additional lengths +// are ignored +// +// values: length length +// default: 0em 0ex If frame attribute is "none" or not specified, +// 0.4em 0.5ex otherwise +// +// Unitless values are permitted and provide a multiple of the default value +// Negative values are forbidden. +// + +static const float kDefaultRowspacingEx = 1.0f; +static const float kDefaultColumnspacingEm = 0.8f; +static const float kDefaultFramespacingArg0Em = 0.4f; +static const float kDefaultFramespacingArg1Ex = 0.5f; + +static void ExtractSpacingValues(const nsAString& aString, nsAtom* aAttribute, + nsTArray<nscoord>& aSpacingArray, + nsIFrame* aFrame, nscoord aDefaultValue0, + nscoord aDefaultValue1, + float aFontSizeInflation) { + nsPresContext* presContext = aFrame->PresContext(); + ComputedStyle* computedStyle = aFrame->Style(); + + const char16_t* start = aString.BeginReading(); + const char16_t* end = aString.EndReading(); + + int32_t startIndex = 0; + int32_t count = 0; + int32_t elementNum = 0; + + while (start < end) { + // Skip leading spaces. + while ((start < end) && nsCRT::IsAsciiSpace(*start)) { + start++; + startIndex++; + } + + // Look for the end of the string, or another space. + while ((start < end) && !nsCRT::IsAsciiSpace(*start)) { + start++; + count++; + } + + // Grab the value found and process it. + if (count > 0) { + const nsAString& str = Substring(aString, startIndex, count); + nsAutoString valueString; + valueString.Assign(str); + nscoord newValue; + if (aAttribute == nsGkAtoms::framespacing_ && elementNum) { + newValue = aDefaultValue1; + } else { + newValue = aDefaultValue0; + } + nsMathMLFrame::ParseNumericValue(valueString, &newValue, 0, presContext, + computedStyle, aFontSizeInflation); + aSpacingArray.AppendElement(newValue); + + startIndex += count; + count = 0; + elementNum++; + } + } +} + +static void ParseSpacingAttribute(nsMathMLmtableFrame* aFrame, + nsAtom* aAttribute) { + NS_ASSERTION(aAttribute == nsGkAtoms::rowspacing_ || + aAttribute == nsGkAtoms::columnspacing_ || + aAttribute == nsGkAtoms::framespacing_, + "Non spacing attribute passed"); + + nsAutoString attrValue; + Element* frameElement = aFrame->GetContent()->AsElement(); + frameElement->GetAttr(aAttribute, attrValue); + + if (nsGkAtoms::framespacing_ == aAttribute) { + nsAutoString frame; + frameElement->GetAttr(nsGkAtoms::frame, frame); + if (frame.IsEmpty() || frame.EqualsLiteral("none")) { + aFrame->SetFrameSpacing(0, 0); + return; + } + } + + nscoord value; + nscoord value2; + // Set defaults + float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(aFrame); + RefPtr<nsFontMetrics> fm = + nsLayoutUtils::GetFontMetricsForFrame(aFrame, fontSizeInflation); + if (nsGkAtoms::rowspacing_ == aAttribute) { + value = kDefaultRowspacingEx * fm->XHeight(); + value2 = 0; + } else if (nsGkAtoms::columnspacing_ == aAttribute) { + value = kDefaultColumnspacingEm * fm->EmHeight(); + value2 = 0; + } else { + value = kDefaultFramespacingArg0Em * fm->EmHeight(); + value2 = kDefaultFramespacingArg1Ex * fm->XHeight(); + } + + nsTArray<nscoord> valueList; + ExtractSpacingValues(attrValue, aAttribute, valueList, aFrame, value, value2, + fontSizeInflation); + if (valueList.Length() == 0) { + if (frameElement->HasAttr(aAttribute)) { + ReportParseError(aFrame, aAttribute->GetUTF16String(), attrValue.get()); + } + valueList.AppendElement(value); + } + if (aAttribute == nsGkAtoms::framespacing_) { + if (valueList.Length() == 1) { + if (frameElement->HasAttr(aAttribute)) { + ReportParseError(aFrame, aAttribute->GetUTF16String(), attrValue.get()); + } + valueList.AppendElement(value2); + } else if (valueList.Length() != 2) { + ReportParseError(aFrame, aAttribute->GetUTF16String(), attrValue.get()); + } + } + + if (aAttribute == nsGkAtoms::rowspacing_) { + aFrame->SetRowSpacingArray(valueList); + } else if (aAttribute == nsGkAtoms::columnspacing_) { + aFrame->SetColSpacingArray(valueList); + } else { + aFrame->SetFrameSpacing(valueList.ElementAt(0), valueList.ElementAt(1)); + } +} + +static void ParseSpacingAttributes(nsMathMLmtableFrame* aTableFrame) { + ParseSpacingAttribute(aTableFrame, nsGkAtoms::rowspacing_); + ParseSpacingAttribute(aTableFrame, nsGkAtoms::columnspacing_); + ParseSpacingAttribute(aTableFrame, nsGkAtoms::framespacing_); + aTableFrame->SetUseCSSSpacing(); +} + +// map all attributes within a table -- requires the indices of rows and cells. +// so it can only happen after they are made ready by the table base class. +static void MapAllAttributesIntoCSS(nsMathMLmtableFrame* aTableFrame) { + // Map mtable rowalign & rowlines. + ParseFrameAttribute(aTableFrame, nsGkAtoms::rowalign_, true); + ParseFrameAttribute(aTableFrame, nsGkAtoms::rowlines_, true); + + // Map mtable columnalign & columnlines. + ParseFrameAttribute(aTableFrame, nsGkAtoms::columnalign_, true); + ParseFrameAttribute(aTableFrame, nsGkAtoms::columnlines_, true); + + // Map mtable rowspacing, columnspacing & framespacing + ParseSpacingAttributes(aTableFrame); + + // mtable is simple and only has one (pseudo) row-group + nsIFrame* rgFrame = aTableFrame->PrincipalChildList().FirstChild(); + if (!rgFrame || !rgFrame->IsTableRowGroupFrame()) return; + + for (nsIFrame* rowFrame : rgFrame->PrincipalChildList()) { + DEBUG_VERIFY_THAT_FRAME_IS(rowFrame, TableRow); + if (rowFrame->IsTableRowFrame()) { + // Map row rowalign. + ParseFrameAttribute(rowFrame, nsGkAtoms::rowalign_, false); + // Map row columnalign. + ParseFrameAttribute(rowFrame, nsGkAtoms::columnalign_, true); + + for (nsIFrame* cellFrame : rowFrame->PrincipalChildList()) { + DEBUG_VERIFY_THAT_FRAME_IS(cellFrame, TableCell); + if (cellFrame->IsTableCellFrame()) { + // Map cell rowalign. + ParseFrameAttribute(cellFrame, nsGkAtoms::rowalign_, false); + // Map row columnalign. + ParseFrameAttribute(cellFrame, nsGkAtoms::columnalign_, false); + } + } + } + } +} + +// the align attribute of mtable can have a row number which indicates +// from where to anchor the table, e.g., top 5 means anchor the table at +// the top of the 5th row, axis -1 means anchor the table on the axis of +// the last row + +// The REC says that the syntax is +// '\s*(top|bottom|center|baseline|axis)(\s+-?[0-9]+)?\s*' +// the parsing could have been simpler with that syntax +// but for backward compatibility we make optional +// the whitespaces between the alignment name and the row number + +enum eAlign { + eAlign_top, + eAlign_bottom, + eAlign_center, + eAlign_baseline, + eAlign_axis +}; + +static void ParseAlignAttribute(nsString& aValue, eAlign& aAlign, + int32_t& aRowIndex) { + // by default, the table is centered about the axis + aRowIndex = 0; + aAlign = eAlign_axis; + int32_t len = 0; + + // we only have to remove the leading spaces because + // ToInteger ignores the whitespaces around the number + aValue.CompressWhitespace(true, false); + + if (0 == aValue.Find(u"top")) { + len = 3; // 3 is the length of 'top' + aAlign = eAlign_top; + } else if (0 == aValue.Find(u"bottom")) { + len = 6; // 6 is the length of 'bottom' + aAlign = eAlign_bottom; + } else if (0 == aValue.Find(u"center")) { + len = 6; // 6 is the length of 'center' + aAlign = eAlign_center; + } else if (0 == aValue.Find(u"baseline")) { + len = 8; // 8 is the length of 'baseline' + aAlign = eAlign_baseline; + } else if (0 == aValue.Find(u"axis")) { + len = 4; // 4 is the length of 'axis' + aAlign = eAlign_axis; + } + if (len) { + nsresult error; + aValue.Cut(0, len); // aValue is not a const here + aRowIndex = aValue.ToInteger(&error); + if (NS_FAILED(error)) aRowIndex = 0; + } +} + +#ifdef DEBUG_rbs_off +// call ListMathMLTree(mParent) to get the big picture +static void ListMathMLTree(nsIFrame* atLeast) { + // climb up to <math> or <body> if <math> isn't there + nsIFrame* f = atLeast; + for (; f; f = f->GetParent()) { + nsIContent* c = f->GetContent(); + if (!c || c->IsMathMLElement(nsGkAtoms::math) || + // XXXbaku which kind of body tag? + c->NodeInfo()->NameAtom(nsGkAtoms::body)) + break; + } + if (!f) f = atLeast; + f->List(stdout, 0); +} +#endif + +// -------- +// implementation of nsMathMLmtableWrapperFrame + +NS_QUERYFRAME_HEAD(nsMathMLmtableWrapperFrame) + NS_QUERYFRAME_ENTRY(nsIMathMLFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsTableWrapperFrame) + +nsContainerFrame* NS_NewMathMLmtableOuterFrame(PresShell* aPresShell, + ComputedStyle* aStyle) { + return new (aPresShell) + nsMathMLmtableWrapperFrame(aStyle, aPresShell->GetPresContext()); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmtableWrapperFrame) + +nsMathMLmtableWrapperFrame::~nsMathMLmtableWrapperFrame() = default; + +nsresult nsMathMLmtableWrapperFrame::AttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType) { + // Attributes specific to <mtable>: + // frame : in mathml.css + // framespacing : here + // groupalign : not yet supported + // equalrows : not yet supported + // equalcolumns : not yet supported + // displaystyle : here and in mathml.css + // align : in reflow + // rowalign : here + // rowlines : here + // rowspacing : here + // columnalign : here + // columnlines : here + // columnspacing : here + + // mtable is simple and only has one (pseudo) row-group inside our inner-table + nsIFrame* tableFrame = mFrames.FirstChild(); + NS_ASSERTION(tableFrame && tableFrame->IsTableFrame(), + "should always have an inner table frame"); + nsIFrame* rgFrame = tableFrame->PrincipalChildList().FirstChild(); + if (!rgFrame || !rgFrame->IsTableRowGroupFrame()) return NS_OK; + + // align - just need to issue a dirty (resize) reflow command + if (aAttribute == nsGkAtoms::align) { + PresShell()->FrameNeedsReflow(this, IntrinsicDirty::None, + NS_FRAME_IS_DIRTY); + return NS_OK; + } + + // displaystyle - may seem innocuous, but it is actually very harsh -- + // like changing an unit. Blow away and recompute all our automatic + // presentational data, and issue a style-changed reflow request + if (aAttribute == nsGkAtoms::displaystyle_) { + nsMathMLContainerFrame::RebuildAutomaticDataForChildren(GetParent()); + // Need to reflow the parent, not us, because this can actually + // affect siblings. + PresShell()->FrameNeedsReflow(GetParent(), + IntrinsicDirty::FrameAncestorsAndDescendants, + NS_FRAME_IS_DIRTY); + return NS_OK; + } + + // ...and the other attributes affect rows or columns in one way or another + + if (aAttribute == nsGkAtoms::rowspacing_ || + aAttribute == nsGkAtoms::columnspacing_ || + aAttribute == nsGkAtoms::framespacing_) { + nsMathMLmtableFrame* mathMLmtableFrame = do_QueryFrame(tableFrame); + if (mathMLmtableFrame) { + ParseSpacingAttribute(mathMLmtableFrame, aAttribute); + mathMLmtableFrame->SetUseCSSSpacing(); + } + } else if (aAttribute == nsGkAtoms::rowalign_ || + aAttribute == nsGkAtoms::rowlines_ || + aAttribute == nsGkAtoms::columnalign_ || + aAttribute == nsGkAtoms::columnlines_) { + // clear any cached property list for this table + tableFrame->RemoveProperty(AttributeToProperty(aAttribute)); + // Reparse the new attribute on the table. + ParseFrameAttribute(tableFrame, aAttribute, true); + } else { + // Ignore attributes that do not affect layout. + return NS_OK; + } + + // Explicitly request a reflow in our subtree to pick up any changes + PresShell()->FrameNeedsReflow( + this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY); + + return NS_OK; +} + +nsIFrame* nsMathMLmtableWrapperFrame::GetRowFrameAt(int32_t aRowIndex) { + int32_t rowCount = GetRowCount(); + + // Negative indices mean to find upwards from the end. + if (aRowIndex < 0) { + aRowIndex = rowCount + aRowIndex; + } else { + // aRowIndex is 1-based, so convert it to a 0-based index + --aRowIndex; + } + + // if our inner table says that the index is valid, find the row now + if (0 <= aRowIndex && aRowIndex <= rowCount) { + nsIFrame* tableFrame = mFrames.FirstChild(); + NS_ASSERTION(tableFrame && tableFrame->IsTableFrame(), + "should always have an inner table frame"); + nsIFrame* rgFrame = tableFrame->PrincipalChildList().FirstChild(); + if (!rgFrame || !rgFrame->IsTableRowGroupFrame()) return nullptr; + for (nsIFrame* rowFrame : rgFrame->PrincipalChildList()) { + if (aRowIndex == 0) { + DEBUG_VERIFY_THAT_FRAME_IS(rowFrame, TableRow); + if (!rowFrame->IsTableRowFrame()) return nullptr; + + return rowFrame; + } + --aRowIndex; + } + } + return nullptr; +} + +void nsMathMLmtableWrapperFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) { + MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); + + nsAutoString value; + // we want to return a table that is anchored according to the align attribute + + nsTableWrapperFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, + aStatus); + NS_ASSERTION(aDesiredSize.Height() >= 0, "illegal height for mtable"); + NS_ASSERTION(aDesiredSize.Width() >= 0, "illegal width for mtable"); + + // see if the user has set the align attribute on the <mtable> + int32_t rowIndex = 0; + eAlign tableAlign = eAlign_axis; + mContent->AsElement()->GetAttr(nsGkAtoms::align, value); + if (!value.IsEmpty()) { + ParseAlignAttribute(value, tableAlign, rowIndex); + } + + // adjustments if there is a specified row from where to anchor the table + // (conceptually: when there is no row of reference, picture the table as if + // it is wrapped in a single big fictional row at dy = 0, this way of + // doing so allows us to have a single code path for all cases). + nscoord dy = 0; + WritingMode wm = aDesiredSize.GetWritingMode(); + nscoord blockSize = aDesiredSize.BSize(wm); + nsIFrame* rowFrame = nullptr; + if (rowIndex) { + rowFrame = GetRowFrameAt(rowIndex); + if (rowFrame) { + // translate the coordinates to be relative to us and in our writing mode + nsIFrame* frame = rowFrame; + LogicalRect rect(wm, frame->GetRect(), + aReflowInput.ComputedSizeAsContainerIfConstrained()); + blockSize = rect.BSize(wm); + do { + nsIFrame* parent = frame->GetParent(); + dy += frame->BStart(wm, parent->GetSize()); + frame = parent; + } while (frame != this); + } + } + switch (tableAlign) { + case eAlign_top: + aDesiredSize.SetBlockStartAscent(dy); + break; + case eAlign_bottom: + aDesiredSize.SetBlockStartAscent(dy + blockSize); + break; + case eAlign_center: + aDesiredSize.SetBlockStartAscent(dy + blockSize / 2); + break; + case eAlign_baseline: + if (rowFrame) { + // anchor the table on the baseline of the row of reference + nscoord rowAscent = ((nsTableRowFrame*)rowFrame)->GetMaxCellAscent(); + if (rowAscent) { // the row has at least one cell with 'vertical-align: + // baseline' + aDesiredSize.SetBlockStartAscent(dy + rowAscent); + break; + } + } + // in other situations, fallback to center + aDesiredSize.SetBlockStartAscent(dy + blockSize / 2); + break; + case eAlign_axis: + default: { + // XXX should instead use style data from the row of reference here ? + RefPtr<nsFontMetrics> fm = + nsLayoutUtils::GetInflatedFontMetricsForFrame(this); + nscoord axisHeight; + GetAxisHeight(aReflowInput.mRenderingContext->GetDrawTarget(), fm, + axisHeight); + if (rowFrame) { + // anchor the table on the axis of the row of reference + // XXX fallback to baseline because it is a hard problem + // XXX need to fetch the axis of the row; would need rowalign=axis to + // work better + nscoord rowAscent = ((nsTableRowFrame*)rowFrame)->GetMaxCellAscent(); + if (rowAscent) { // the row has at least one cell with 'vertical-align: + // baseline' + aDesiredSize.SetBlockStartAscent(dy + rowAscent); + break; + } + } + // in other situations, fallback to using half of the height + aDesiredSize.SetBlockStartAscent(dy + blockSize / 2 + axisHeight); + } + } + + mReference.x = 0; + mReference.y = aDesiredSize.BlockStartAscent(); + + // just make-up a bounding metrics + mBoundingMetrics = nsBoundingMetrics(); + mBoundingMetrics.ascent = aDesiredSize.BlockStartAscent(); + mBoundingMetrics.descent = + aDesiredSize.Height() - aDesiredSize.BlockStartAscent(); + mBoundingMetrics.width = aDesiredSize.Width(); + mBoundingMetrics.leftBearing = 0; + mBoundingMetrics.rightBearing = aDesiredSize.Width(); + + aDesiredSize.mBoundingMetrics = mBoundingMetrics; +} + +nsContainerFrame* NS_NewMathMLmtableFrame(PresShell* aPresShell, + ComputedStyle* aStyle) { + return new (aPresShell) + nsMathMLmtableFrame(aStyle, aPresShell->GetPresContext()); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmtableFrame) + +nsMathMLmtableFrame::~nsMathMLmtableFrame() = default; + +void nsMathMLmtableFrame::SetInitialChildList(ChildListID aListID, + nsFrameList&& aChildList) { + nsTableFrame::SetInitialChildList(aListID, std::move(aChildList)); + MapAllAttributesIntoCSS(this); +} + +void nsMathMLmtableFrame::RestyleTable() { + // re-sync MathML specific style data that may have changed + MapAllAttributesIntoCSS(this); + + // Explicitly request a re-resolve and reflow in our subtree to pick up any + // changes + PresContext()->RestyleManager()->PostRestyleEvent( + mContent->AsElement(), RestyleHint::RestyleSubtree(), + nsChangeHint_AllReflowHints); +} + +nscoord nsMathMLmtableFrame::GetColSpacing(int32_t aColIndex) { + if (mUseCSSSpacing) { + return nsTableFrame::GetColSpacing(aColIndex); + } + if (!mColSpacing.Length()) { + NS_ERROR("mColSpacing should not be empty"); + return 0; + } + if (aColIndex < 0 || aColIndex >= GetColCount()) { + NS_ASSERTION(aColIndex == -1 || aColIndex == GetColCount(), + "Desired column beyond bounds of table and border"); + return mFrameSpacingX; + } + if ((uint32_t)aColIndex >= mColSpacing.Length()) { + return mColSpacing.LastElement(); + } + return mColSpacing.ElementAt(aColIndex); +} + +nscoord nsMathMLmtableFrame::GetColSpacing(int32_t aStartColIndex, + int32_t aEndColIndex) { + if (mUseCSSSpacing) { + return nsTableFrame::GetColSpacing(aStartColIndex, aEndColIndex); + } + if (aStartColIndex == aEndColIndex) { + return 0; + } + if (!mColSpacing.Length()) { + NS_ERROR("mColSpacing should not be empty"); + return 0; + } + nscoord space = 0; + if (aStartColIndex < 0) { + NS_ASSERTION(aStartColIndex == -1, + "Desired column beyond bounds of table and border"); + space += mFrameSpacingX; + aStartColIndex = 0; + } + if (aEndColIndex >= GetColCount()) { + NS_ASSERTION(aEndColIndex == GetColCount(), + "Desired column beyond bounds of table and border"); + space += mFrameSpacingX; + aEndColIndex = GetColCount(); + } + // Only iterate over column spacing when there is the potential to vary + int32_t min = std::min(aEndColIndex, (int32_t)mColSpacing.Length()); + for (int32_t i = aStartColIndex; i < min; i++) { + space += mColSpacing.ElementAt(i); + } + // The remaining values are constant. Note that if there are more + // column spacings specified than there are columns, LastElement() will be + // multiplied by 0, so it is still safe to use. + space += (aEndColIndex - min) * mColSpacing.LastElement(); + return space; +} + +nscoord nsMathMLmtableFrame::GetRowSpacing(int32_t aRowIndex) { + if (mUseCSSSpacing) { + return nsTableFrame::GetRowSpacing(aRowIndex); + } + if (!mRowSpacing.Length()) { + NS_ERROR("mRowSpacing should not be empty"); + return 0; + } + if (aRowIndex < 0 || aRowIndex >= GetRowCount()) { + NS_ASSERTION(aRowIndex == -1 || aRowIndex == GetRowCount(), + "Desired row beyond bounds of table and border"); + return mFrameSpacingY; + } + if ((uint32_t)aRowIndex >= mRowSpacing.Length()) { + return mRowSpacing.LastElement(); + } + return mRowSpacing.ElementAt(aRowIndex); +} + +nscoord nsMathMLmtableFrame::GetRowSpacing(int32_t aStartRowIndex, + int32_t aEndRowIndex) { + if (mUseCSSSpacing) { + return nsTableFrame::GetRowSpacing(aStartRowIndex, aEndRowIndex); + } + if (aStartRowIndex == aEndRowIndex) { + return 0; + } + if (!mRowSpacing.Length()) { + NS_ERROR("mRowSpacing should not be empty"); + return 0; + } + nscoord space = 0; + if (aStartRowIndex < 0) { + NS_ASSERTION(aStartRowIndex == -1, + "Desired row beyond bounds of table and border"); + space += mFrameSpacingY; + aStartRowIndex = 0; + } + if (aEndRowIndex >= GetRowCount()) { + NS_ASSERTION(aEndRowIndex == GetRowCount(), + "Desired row beyond bounds of table and border"); + space += mFrameSpacingY; + aEndRowIndex = GetRowCount(); + } + // Only iterate over row spacing when there is the potential to vary + int32_t min = std::min(aEndRowIndex, (int32_t)mRowSpacing.Length()); + for (int32_t i = aStartRowIndex; i < min; i++) { + space += mRowSpacing.ElementAt(i); + } + // The remaining values are constant. Note that if there are more + // row spacings specified than there are row, LastElement() will be + // multiplied by 0, so it is still safe to use. + space += (aEndRowIndex - min) * mRowSpacing.LastElement(); + return space; +} + +void nsMathMLmtableFrame::SetUseCSSSpacing() { + mUseCSSSpacing = !(mContent->AsElement()->HasAttr(nsGkAtoms::rowspacing_) || + mContent->AsElement()->HasAttr( + kNameSpaceID_None, nsGkAtoms::columnspacing_) || + mContent->AsElement()->HasAttr(nsGkAtoms::framespacing_)); +} + +NS_QUERYFRAME_HEAD(nsMathMLmtableFrame) + NS_QUERYFRAME_ENTRY(nsMathMLmtableFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsTableFrame) + +// -------- +// implementation of nsMathMLmtrFrame + +nsContainerFrame* NS_NewMathMLmtrFrame(PresShell* aPresShell, + ComputedStyle* aStyle) { + return new (aPresShell) + nsMathMLmtrFrame(aStyle, aPresShell->GetPresContext()); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmtrFrame) + +nsMathMLmtrFrame::~nsMathMLmtrFrame() = default; + +nsresult nsMathMLmtrFrame::AttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType) { + // Attributes specific to <mtr>: + // groupalign : Not yet supported. + // rowalign : Here + // columnalign : Here + + if (aAttribute != nsGkAtoms::rowalign_ && + aAttribute != nsGkAtoms::columnalign_) { + return NS_OK; + } + + RemoveProperty(AttributeToProperty(aAttribute)); + + bool allowMultiValues = (aAttribute == nsGkAtoms::columnalign_); + + // Reparse the new attribute. + ParseFrameAttribute(this, aAttribute, allowMultiValues); + + // Explicitly request a reflow in our subtree to pick up any changes + PresShell()->FrameNeedsReflow( + this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY); + + return NS_OK; +} + +// -------- +// implementation of nsMathMLmtdFrame + +nsContainerFrame* NS_NewMathMLmtdFrame(PresShell* aPresShell, + ComputedStyle* aStyle, + nsTableFrame* aTableFrame) { + return new (aPresShell) nsMathMLmtdFrame(aStyle, aTableFrame); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmtdFrame) + +nsMathMLmtdFrame::~nsMathMLmtdFrame() = default; + +void nsMathMLmtdFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + nsTableCellFrame::Init(aContent, aParent, aPrevInFlow); + + // We want to use the ancestor <math> element's font inflation to avoid + // individual cells having their own varying font inflation. + RemoveStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT); +} + +nsresult nsMathMLmtdFrame::AttributeChanged(int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType) { + // Attributes specific to <mtd>: + // groupalign : Not yet supported + // rowalign : here + // columnalign : here + // rowspan : here + // columnspan : here + + if (aAttribute == nsGkAtoms::rowalign_ || + aAttribute == nsGkAtoms::columnalign_) { + RemoveProperty(AttributeToProperty(aAttribute)); + + // Reparse the attribute. + ParseFrameAttribute(this, aAttribute, false); + return NS_OK; + } + + if (aAttribute == nsGkAtoms::rowspan || + aAttribute == nsGkAtoms::columnspan_) { + // use the naming expected by the base class + if (aAttribute == nsGkAtoms::columnspan_) aAttribute = nsGkAtoms::colspan; + return nsTableCellFrame::AttributeChanged(aNameSpaceID, aAttribute, + aModType); + } + + return NS_OK; +} + +StyleVerticalAlignKeyword nsMathMLmtdFrame::GetVerticalAlign() const { + // Set the default alignment in case no alignment was specified + auto alignment = nsTableCellFrame::GetVerticalAlign(); + + nsTArray<int8_t>* alignmentList = FindCellProperty(this, RowAlignProperty()); + + if (alignmentList) { + uint32_t rowIndex = RowIndex(); + + // If the row number is greater than the number of provided rowalign values, + // we simply repeat the last value. + return static_cast<StyleVerticalAlignKeyword>( + (rowIndex < alignmentList->Length()) + ? alignmentList->ElementAt(rowIndex) + : alignmentList->LastElement()); + } + + return alignment; +} + +void nsMathMLmtdFrame::ProcessBorders(nsTableFrame* aFrame, + nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) { + aLists.BorderBackground()->AppendNewToTop<nsDisplaymtdBorder>(aBuilder, this); +} + +LogicalMargin nsMathMLmtdFrame::GetBorderWidth(WritingMode aWM) const { + nsStyleBorder styleBorder = *StyleBorder(); + ApplyBorderToStyle(this, styleBorder); + return LogicalMargin(aWM, styleBorder.GetComputedBorder()); +} + +nsMargin nsMathMLmtdFrame::GetBorderOverflow() { + nsStyleBorder styleBorder = *StyleBorder(); + ApplyBorderToStyle(this, styleBorder); + nsMargin overflow = ComputeBorderOverflow(this, styleBorder); + return overflow; +} + +// -------- +// implementation of nsMathMLmtdInnerFrame + +NS_QUERYFRAME_HEAD(nsMathMLmtdInnerFrame) + NS_QUERYFRAME_ENTRY(nsIMathMLFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame) + +nsContainerFrame* NS_NewMathMLmtdInnerFrame(PresShell* aPresShell, + ComputedStyle* aStyle) { + return new (aPresShell) + nsMathMLmtdInnerFrame(aStyle, aPresShell->GetPresContext()); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsMathMLmtdInnerFrame) + +nsMathMLmtdInnerFrame::nsMathMLmtdInnerFrame(ComputedStyle* aStyle, + nsPresContext* aPresContext) + : nsBlockFrame(aStyle, aPresContext, kClassID) + // Make a copy of the parent nsStyleText for later modification. + , + mUniqueStyleText(MakeUnique<nsStyleText>(*StyleText())) {} + +void nsMathMLmtdInnerFrame::Reflow(nsPresContext* aPresContext, + ReflowOutput& aDesiredSize, + const ReflowInput& aReflowInput, + nsReflowStatus& aStatus) { + // Let the base class do the reflow + nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus); + + // more about <maligngroup/> and <malignmark/> later + // ... +} + +const nsStyleText* nsMathMLmtdInnerFrame::StyleTextForLineLayout() { + // Set the default alignment in case nothing was specified + auto alignment = uint8_t(StyleText()->mTextAlign); + + nsTArray<int8_t>* alignmentList = + FindCellProperty(this, ColumnAlignProperty()); + + if (alignmentList) { + nsMathMLmtdFrame* cellFrame = (nsMathMLmtdFrame*)GetParent(); + uint32_t columnIndex = cellFrame->ColIndex(); + + // If the column number is greater than the number of provided columalign + // values, we simply repeat the last value. + if (columnIndex < alignmentList->Length()) + alignment = alignmentList->ElementAt(columnIndex); + else + alignment = alignmentList->ElementAt(alignmentList->Length() - 1); + } + + mUniqueStyleText->mTextAlign = StyleTextAlign(alignment); + return mUniqueStyleText.get(); +} + +/* virtual */ +void nsMathMLmtdInnerFrame::DidSetComputedStyle( + ComputedStyle* aOldComputedStyle) { + nsBlockFrame::DidSetComputedStyle(aOldComputedStyle); + mUniqueStyleText = MakeUnique<nsStyleText>(*StyleText()); +} |