mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-02 18:08:58 +00:00
d75426f579
Just drive-by cleanup. Differential Revision: https://phabricator.services.mozilla.com/D226119
1194 lines
42 KiB
C++
1194 lines
42 KiB
C++
/* -*- 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) {}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
// --------
|
|
// 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 (aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::align) {
|
|
PresShell()->FrameNeedsReflow(this, IntrinsicDirty::None,
|
|
NS_FRAME_IS_DIRTY);
|
|
return NS_OK;
|
|
}
|
|
|
|
// ...and the other attributes affect rows or columns in one way or another
|
|
|
|
if (aNameSpaceID == kNameSpaceID_None &&
|
|
(aAttribute == nsGkAtoms::rowspacing_ ||
|
|
aAttribute == nsGkAtoms::columnspacing_ ||
|
|
aAttribute == nsGkAtoms::framespacing_)) {
|
|
nsMathMLmtableFrame* mathMLmtableFrame = do_QueryFrame(tableFrame);
|
|
if (mathMLmtableFrame) {
|
|
ParseSpacingAttribute(mathMLmtableFrame, aAttribute);
|
|
mathMLmtableFrame->SetUseCSSSpacing();
|
|
}
|
|
PresShell()->FrameNeedsReflow(
|
|
this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY);
|
|
return NS_OK;
|
|
}
|
|
|
|
if (aNameSpaceID == kNameSpaceID_None &&
|
|
(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);
|
|
PresShell()->FrameNeedsReflow(
|
|
this, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY);
|
|
return NS_OK;
|
|
}
|
|
|
|
// Skip nsTableWrapperFrame::AttributeChanged, mtable does not share more
|
|
// attributes with table.
|
|
return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
|
|
}
|
|
|
|
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 (aNameSpaceID != kNameSpaceID_None ||
|
|
(aAttribute != nsGkAtoms::rowalign_ &&
|
|
aAttribute != nsGkAtoms::columnalign_)) {
|
|
// Skip nsTableCellFrame::AttributeChanged, mtr does not share any attribute
|
|
// with tr.
|
|
return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
|
|
aModType);
|
|
}
|
|
|
|
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 (aNameSpaceID == kNameSpaceID_None &&
|
|
(aAttribute == nsGkAtoms::rowalign_ ||
|
|
aAttribute == nsGkAtoms::columnalign_)) {
|
|
RemoveProperty(AttributeToProperty(aAttribute));
|
|
|
|
// Reparse the attribute.
|
|
ParseFrameAttribute(this, aAttribute, false);
|
|
return NS_OK;
|
|
}
|
|
|
|
if (aNameSpaceID == kNameSpaceID_None &&
|
|
(aAttribute == nsGkAtoms::rowspan ||
|
|
aAttribute == nsGkAtoms::columnspan_)) {
|
|
// nsTableCellFrame takes care of renaming columnspan to colspan.
|
|
return nsTableCellFrame::AttributeChanged(aNameSpaceID, aAttribute,
|
|
aModType);
|
|
}
|
|
|
|
// Skip nsTableCellFrame::AttributeChanged, mtd does not share more attributes
|
|
// with td.
|
|
return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
|
|
}
|
|
|
|
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());
|
|
}
|