Commit ebf91dd9 authored by Rob Buis's avatar Rob Buis Committed by Commit Bot

[mathml] Implement compact operator dictionary

Implement compact operator dictionary as specified [1]. Clients of
the operator dictionary can query operator values using the
HasBooleanProperty method. When attribute values (stretchy, form,
symmetric, largeop or movablelimits) are set or changed, dirty flags
are used to efficiently recompute the operator dictionary.

[1] https://mathml-refresh.github.io/mathml-core/#operator-dictionary

Bug: 6606
Change-Id: I1c24cc1e91599abe985efbc1102125261d3beba3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2368362
Commit-Queue: Rob Buis <rbuis@igalia.com>
Reviewed-by: default avatarDominik Röttsches <drott@chromium.org>
Reviewed-by: default avatarIan Kilpatrick <ikilpatrick@chromium.org>
Reviewed-by: default avatarFrédéric Wang <fwang@igalia.com>
Cr-Commit-Position: refs/heads/master@{#803797}
parent 245b85c2
...@@ -12,7 +12,9 @@ ...@@ -12,7 +12,9 @@
"display", "display",
"displaystyle", "displaystyle",
"encoding", "encoding",
"form",
"height", "height",
"largeop",
"linethickness", "linethickness",
"lspace", "lspace",
"mathbackground", "mathbackground",
...@@ -21,8 +23,11 @@ ...@@ -21,8 +23,11 @@
"mathvariant", "mathvariant",
"maxsize", "maxsize",
"minsize", "minsize",
"movablelimits",
"rspace", "rspace",
"scriptlevel", "scriptlevel",
"stretchy",
"symmetric",
"voffset", "voffset",
"width", "width",
], ],
......
...@@ -4,12 +4,249 @@ ...@@ -4,12 +4,249 @@
#include "third_party/blink/renderer/core/mathml/mathml_operator_element.h" #include "third_party/blink/renderer/core/mathml/mathml_operator_element.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/style/computed_style.h" #include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/platform/text/mathml_operator_dictionary.h"
namespace blink { namespace blink {
namespace {
static const uint32_t kOperatorPropertyFlagsAll =
MathMLOperatorElement::kStretchy | MathMLOperatorElement::kSymmetric |
MathMLOperatorElement::kLargeOp | MathMLOperatorElement::kMovableLimits;
static const uint32_t kOperatorPropertyFlagsNone = 0;
UChar32 OperatorCodepoint(const String& text_content) {
DCHECK(!text_content.Is8Bit());
auto content_length = text_content.length();
// Reject malformed UTF-16 and operator strings consisting of more than one
// codepoint.
if ((content_length > 2) || (content_length == 0) ||
(content_length == 1 && !U16_IS_SINGLE(text_content[0])) ||
(content_length == 2 && !U16_IS_LEAD(text_content[0])))
return kNonCharacter;
UChar32 character;
size_t offset = 0;
U16_NEXT(text_content, offset, content_length, character);
return character;
}
// https://mathml-refresh.github.io/mathml-core/#operator-dictionary-categories-values
// Leading and trailing spaces are respresented in math units, i.e. 1/18em.
struct MathMLOperatorDictionaryProperties {
unsigned leading_space_in_math_unit : 3;
unsigned trailing_space_in_math_unit : 3;
unsigned flags : 4;
};
static const MathMLOperatorDictionaryProperties
MathMLOperatorDictionaryCategories[] = {
{5, 5, kOperatorPropertyFlagsNone}, // None (default values)
{5, 5, MathMLOperatorElement::kStretchy}, // Category A
{4, 4, kOperatorPropertyFlagsNone}, // Category B
{3, 3, kOperatorPropertyFlagsNone}, // Category C
{0, 0, kOperatorPropertyFlagsNone}, // Categories D, E, L
{0, 0,
MathMLOperatorElement::kStretchy |
MathMLOperatorElement::kSymmetric}, // Categories F, G
{3, 3,
MathMLOperatorElement::kSymmetric |
MathMLOperatorElement::kLargeOp}, // Category H
{0, 0, MathMLOperatorElement::kStretchy}, // Category I
{3, 3,
MathMLOperatorElement::kSymmetric | MathMLOperatorElement::kLargeOp |
MathMLOperatorElement::kMovableLimits}, // Category J
{3, 0, kOperatorPropertyFlagsNone}, // Category K
{0, 3, kOperatorPropertyFlagsNone}, // Category M
};
MathMLOperatorElement::OperatorContent ParseOperatorContent(
const String& string) {
MathMLOperatorElement::OperatorContent operator_content;
operator_content.characters = string;
operator_content.characters.Ensure16Bit();
operator_content.is_vertical = Character::IsVerticalMathCharacter(
OperatorCodepoint(operator_content.characters));
return operator_content;
}
static const QualifiedName& OperatorPropertyFlagToAttributeName(
MathMLOperatorElement::OperatorPropertyFlag flag) {
switch (flag) {
case MathMLOperatorElement::kLargeOp:
return mathml_names::kLargeopAttr;
case MathMLOperatorElement::kMovableLimits:
return mathml_names::kMovablelimitsAttr;
case MathMLOperatorElement::kStretchy:
return mathml_names::kStretchyAttr;
case MathMLOperatorElement::kSymmetric:
return mathml_names::kSymmetricAttr;
}
NOTREACHED();
return g_null_name;
}
} // namespace
MathMLOperatorElement::MathMLOperatorElement(Document& doc) MathMLOperatorElement::MathMLOperatorElement(Document& doc)
: MathMLElement(mathml_names::kMoTag, doc) {} : MathMLElement(mathml_names::kMoTag, doc) {
operator_content_ = base::nullopt;
properties_.dictionary_category =
MathMLOperatorDictionaryCategory::kUndefined;
properties_.dirty_flags = kOperatorPropertyFlagsAll;
}
void MathMLOperatorElement::SetOperatorPropertyDirtyFlagIfNeeded(
const AttributeModificationParams& param,
const OperatorPropertyFlag& flag,
bool& needs_layout) {
needs_layout = param.new_value != param.old_value;
if (needs_layout)
properties_.dirty_flags |= flag;
}
void MathMLOperatorElement::ParseAttribute(
const AttributeModificationParams& param) {
bool needs_layout = false;
if (param.name == mathml_names::kFormAttr) {
needs_layout = param.new_value != param.old_value;
if (needs_layout) {
SetOperatorFormDirty();
properties_.dirty_flags |= kOperatorPropertyFlagsAll;
}
} else if (param.name == mathml_names::kStretchyAttr) {
SetOperatorPropertyDirtyFlagIfNeeded(
param, MathMLOperatorElement::kStretchy, needs_layout);
} else if (param.name == mathml_names::kSymmetricAttr) {
SetOperatorPropertyDirtyFlagIfNeeded(
param, MathMLOperatorElement::kSymmetric, needs_layout);
} else if (param.name == mathml_names::kLargeopAttr) {
SetOperatorPropertyDirtyFlagIfNeeded(param, MathMLOperatorElement::kLargeOp,
needs_layout);
} else if (param.name == mathml_names::kMovablelimitsAttr) {
SetOperatorPropertyDirtyFlagIfNeeded(
param, MathMLOperatorElement::kMovableLimits, needs_layout);
}
if (needs_layout && GetLayoutObject())
GetLayoutObject()->UpdateFromElement();
MathMLElement::ParseAttribute(param);
}
// https://mathml-refresh.github.io/mathml-core/#dfn-algorithm-for-determining-the-properties-of-an-embellished-operator
void MathMLOperatorElement::ComputeDictionaryCategory() {
if (properties_.dictionary_category !=
MathMLOperatorDictionaryCategory::kUndefined)
return;
// We first determine the form attribute and use the default spacing and
// properties.
// https://mathml-refresh.github.io/mathml-core/#dfn-form
const auto& value = FastGetAttribute(mathml_names::kFormAttr);
bool explicit_form = true;
MathMLOperatorDictionaryForm form;
if (EqualIgnoringASCIICase(value, "prefix")) {
form = MathMLOperatorDictionaryForm::kPrefix;
} else if (EqualIgnoringASCIICase(value, "infix")) {
form = MathMLOperatorDictionaryForm::kInfix;
} else if (EqualIgnoringASCIICase(value, "postfix")) {
form = MathMLOperatorDictionaryForm::kPostfix;
} else {
// TODO(crbug.com/1121113): Implement the remaining rules for determining
// form.
// https://mathml-refresh.github.io/mathml-core/#dfn-algorithm-for-determining-the-form-of-an-embellished-operator
explicit_form = false;
if (!previousSibling() && nextSibling())
form = MathMLOperatorDictionaryForm::kPrefix;
else if (previousSibling() && !nextSibling())
form = MathMLOperatorDictionaryForm::kPostfix;
else
form = MathMLOperatorDictionaryForm::kInfix;
}
// We then try and find an entry in the operator dictionary to override the
// default values.
// https://mathml-refresh.github.io/mathml-core/#dfn-algorithm-for-determining-the-properties-of-an-embellished-operator
auto category = FindCategory(GetOperatorContent().characters, form);
if (category != MathMLOperatorDictionaryCategory::kNone) {
// Step 2.
properties_.dictionary_category = category;
} else {
if (!explicit_form) {
// Step 3.
for (uint8_t fallback_form = MathMLOperatorDictionaryForm::kInfix;
fallback_form <= MathMLOperatorDictionaryForm::kPostfix;
fallback_form++) {
if (fallback_form == form)
continue;
auto category = FindCategory(
GetOperatorContent().characters,
static_cast<MathMLOperatorDictionaryForm>(fallback_form));
if (category != MathMLOperatorDictionaryCategory::kNone) {
properties_.dictionary_category = category;
return;
}
}
// Step 4.
properties_.dictionary_category = MathMLOperatorDictionaryCategory::kNone;
}
}
}
base::Optional<bool> MathMLOperatorElement::BooleanAttribute(
const QualifiedName& name) const {
const AtomicString& value = FastGetAttribute(name);
if (EqualIgnoringASCIICase(value, "true"))
return true;
if (EqualIgnoringASCIICase(value, "false"))
return false;
return base::nullopt;
}
void MathMLOperatorElement::ComputeOperatorProperty(OperatorPropertyFlag flag) {
DCHECK(properties_.dirty_flags & flag);
const auto& name = OperatorPropertyFlagToAttributeName(flag);
if (base::Optional<bool> value = BooleanAttribute(name)) {
// https://mathml-refresh.github.io/mathml-core/#dfn-algorithm-for-determining-the-properties-of-an-embellished-operator
// Step 1.
if (*value) {
properties_.flags |= flag;
} else {
properties_.flags &= ~flag;
}
} else {
// By default, the value specified in the operator dictionary are used.
ComputeDictionaryCategory();
if (MathMLOperatorDictionaryCategories
[std::underlying_type_t<MathMLOperatorDictionaryCategory>(
properties_.dictionary_category)]
.flags &
flag) {
properties_.flags |= flag;
} else {
properties_.flags &= ~flag;
}
}
}
const MathMLOperatorElement::OperatorContent&
MathMLOperatorElement::GetOperatorContent() {
if (!operator_content_)
operator_content_ = ParseOperatorContent(textContent());
return operator_content_.value();
}
bool MathMLOperatorElement::HasBooleanProperty(OperatorPropertyFlag flag) {
if (properties_.dirty_flags & flag) {
ComputeOperatorProperty(flag);
properties_.dirty_flags &= ~flag;
}
return properties_.flags & flag;
}
void MathMLOperatorElement::SetOperatorFormDirty() {
properties_.dictionary_category =
MathMLOperatorDictionaryCategory::kUndefined;
}
void MathMLOperatorElement::AddMathLSpaceIfNeeded( void MathMLOperatorElement::AddMathLSpaceIfNeeded(
ComputedStyle& style, ComputedStyle& style,
......
...@@ -13,18 +13,49 @@ class ComputedStyle; ...@@ -13,18 +13,49 @@ class ComputedStyle;
class CSSToLengthConversionData; class CSSToLengthConversionData;
class Document; class Document;
enum class MathMLOperatorDictionaryCategory : uint8_t;
class CORE_EXPORT MathMLOperatorElement final : public MathMLElement { class CORE_EXPORT MathMLOperatorElement final : public MathMLElement {
public: public:
explicit MathMLOperatorElement(Document&); explicit MathMLOperatorElement(Document&);
struct OperatorContent {
String characters;
bool is_vertical = true;
};
enum OperatorPropertyFlag {
kStretchy = 0x1,
kSymmetric = 0x2,
kLargeOp = 0x4,
kMovableLimits = 0x8,
};
// Query whether given flag is set in the operator dictionary.
bool HasBooleanProperty(OperatorPropertyFlag);
void AddMathLSpaceIfNeeded(ComputedStyle&, const CSSToLengthConversionData&); void AddMathLSpaceIfNeeded(ComputedStyle&, const CSSToLengthConversionData&);
void AddMathRSpaceIfNeeded(ComputedStyle&, const CSSToLengthConversionData&); void AddMathRSpaceIfNeeded(ComputedStyle&, const CSSToLengthConversionData&);
void AddMathMinSizeIfNeeded(ComputedStyle&, const CSSToLengthConversionData&); void AddMathMinSizeIfNeeded(ComputedStyle&, const CSSToLengthConversionData&);
void AddMathMaxSizeIfNeeded(ComputedStyle&, const CSSToLengthConversionData&); void AddMathMaxSizeIfNeeded(ComputedStyle&, const CSSToLengthConversionData&);
private: private:
// FIXME: add operator dictionary. base::Optional<OperatorContent> operator_content_;
// FIXME: add OperatorContent struct. const OperatorContent& GetOperatorContent();
// Operator properties calculated from dictionary and attributes.
// It contains dirty flags to allow efficient dictionary updating.
struct Properties {
MathMLOperatorDictionaryCategory dictionary_category;
unsigned flags : 4;
unsigned dirty_flags : 4;
};
Properties properties_;
void ComputeDictionaryCategory();
void ComputeOperatorProperty(OperatorPropertyFlag);
void SetOperatorFormDirty();
void ParseAttribute(const AttributeModificationParams&) final;
base::Optional<bool> BooleanAttribute(const QualifiedName& name) const;
void SetOperatorPropertyDirtyFlagIfNeeded(const AttributeModificationParams&,
const OperatorPropertyFlag&,
bool& needs_layout);
}; };
} // namespace blink } // namespace blink
......
...@@ -139,6 +139,7 @@ const UChar32 kMathItalicUpperA = 0x1D434; ...@@ -139,6 +139,7 @@ const UChar32 kMathItalicUpperA = 0x1D434;
const UChar32 kMathItalicUpperAlpha = 0x1D6E2; const UChar32 kMathItalicUpperAlpha = 0x1D6E2;
const UChar kMinusSignCharacter = 0x2212; const UChar kMinusSignCharacter = 0x2212;
const UChar kNewlineCharacter = 0x000A; const UChar kNewlineCharacter = 0x000A;
const UChar32 kNonCharacter = 0xFFFF;
const UChar kFormFeedCharacter = 0x000C; const UChar kFormFeedCharacter = 0x000C;
const UChar32 kNabla = 0x2207; const UChar32 kNabla = 0x2207;
const UChar kNationalDigitShapesCharacter = 0x206E; const UChar kNationalDigitShapesCharacter = 0x206E;
...@@ -294,6 +295,7 @@ using WTF::unicode::kNationalDigitShapesCharacter; ...@@ -294,6 +295,7 @@ using WTF::unicode::kNationalDigitShapesCharacter;
using WTF::unicode::kNewlineCharacter; using WTF::unicode::kNewlineCharacter;
using WTF::unicode::kNoBreakSpaceCharacter; using WTF::unicode::kNoBreakSpaceCharacter;
using WTF::unicode::kNominalDigitShapesCharacter; using WTF::unicode::kNominalDigitShapesCharacter;
using WTF::unicode::kNonCharacter;
using WTF::unicode::kObjectReplacementCharacter; using WTF::unicode::kObjectReplacementCharacter;
using WTF::unicode::kParagraphSeparator; using WTF::unicode::kParagraphSeparator;
using WTF::unicode::kPartialDifferential; using WTF::unicode::kPartialDifferential;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment