1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/core/mathml/mathml_operator_element.h"
6
7 #include "third_party/blink/renderer/core/css/style_change_reason.h"
8 #include "third_party/blink/renderer/core/layout/layout_object.h"
9 #include "third_party/blink/renderer/core/style/computed_style.h"
10 #include "third_party/blink/renderer/platform/text/mathml_operator_dictionary.h"
11
12 namespace blink {
13
14 namespace {
15
16 static const uint32_t kOperatorPropertyFlagsAll =
17 MathMLOperatorElement::kStretchy | MathMLOperatorElement::kSymmetric |
18 MathMLOperatorElement::kLargeOp | MathMLOperatorElement::kMovableLimits;
19 static const uint32_t kOperatorPropertyFlagsNone = 0;
20
OperatorCodepoint(const String & text_content)21 UChar32 OperatorCodepoint(const String& text_content) {
22 DCHECK(!text_content.Is8Bit());
23 auto content_length = text_content.length();
24 // Reject malformed UTF-16 and operator strings consisting of more than one
25 // codepoint.
26 if ((content_length > 2) || (content_length == 0) ||
27 (content_length == 1 && !U16_IS_SINGLE(text_content[0])) ||
28 (content_length == 2 && !U16_IS_LEAD(text_content[0])))
29 return kNonCharacter;
30
31 UChar32 character;
32 size_t offset = 0;
33 U16_NEXT(text_content, offset, content_length, character);
34 return character;
35 }
36
37 // https://mathml-refresh.github.io/mathml-core/#operator-dictionary-categories-values
38 // Leading and trailing spaces are respresented in math units, i.e. 1/18em.
39 struct MathMLOperatorDictionaryProperties {
40 unsigned leading_space_in_math_unit : 3;
41 unsigned trailing_space_in_math_unit : 3;
42 unsigned flags : 4;
43 };
44 static const MathMLOperatorDictionaryProperties
45 MathMLOperatorDictionaryCategories[] = {
46 {5, 5, kOperatorPropertyFlagsNone}, // None (default values)
47 {5, 5, MathMLOperatorElement::kStretchy}, // Category A
48 {4, 4, kOperatorPropertyFlagsNone}, // Category B
49 {3, 3, kOperatorPropertyFlagsNone}, // Category C
50 {0, 0, kOperatorPropertyFlagsNone}, // Categories D, E, L
51 {0, 0,
52 MathMLOperatorElement::kStretchy |
53 MathMLOperatorElement::kSymmetric}, // Categories F, G
54 {3, 3,
55 MathMLOperatorElement::kSymmetric |
56 MathMLOperatorElement::kLargeOp}, // Category H
57 {0, 0, MathMLOperatorElement::kStretchy}, // Category I
58 {3, 3,
59 MathMLOperatorElement::kSymmetric | MathMLOperatorElement::kLargeOp |
60 MathMLOperatorElement::kMovableLimits}, // Category J
61 {3, 0, kOperatorPropertyFlagsNone}, // Category K
62 {0, 3, kOperatorPropertyFlagsNone}, // Category M
63 };
64
OperatorPropertyFlagToAttributeName(MathMLOperatorElement::OperatorPropertyFlag flag)65 static const QualifiedName& OperatorPropertyFlagToAttributeName(
66 MathMLOperatorElement::OperatorPropertyFlag flag) {
67 switch (flag) {
68 case MathMLOperatorElement::kLargeOp:
69 return mathml_names::kLargeopAttr;
70 case MathMLOperatorElement::kMovableLimits:
71 return mathml_names::kMovablelimitsAttr;
72 case MathMLOperatorElement::kStretchy:
73 return mathml_names::kStretchyAttr;
74 case MathMLOperatorElement::kSymmetric:
75 return mathml_names::kSymmetricAttr;
76 }
77 NOTREACHED();
78 return g_null_name;
79 }
80
81 } // namespace
82
MathMLOperatorElement(Document & doc)83 MathMLOperatorElement::MathMLOperatorElement(Document& doc)
84 : MathMLElement(mathml_names::kMoTag, doc) {
85 operator_content_ = base::nullopt;
86 properties_.dictionary_category =
87 MathMLOperatorDictionaryCategory::kUndefined;
88 properties_.dirty_flags = kOperatorPropertyFlagsAll;
89 }
90
91 MathMLOperatorElement::OperatorContent
ParseOperatorContent()92 MathMLOperatorElement::ParseOperatorContent() {
93 MathMLOperatorElement::OperatorContent operator_content;
94 if (HasOneTextChild()) {
95 operator_content.characters = textContent();
96 operator_content.characters.Ensure16Bit();
97 operator_content.code_point =
98 OperatorCodepoint(operator_content.characters);
99 operator_content.is_vertical =
100 Character::IsVerticalMathCharacter(operator_content.code_point);
101 }
102 return operator_content;
103 }
104
ChildrenChanged(const ChildrenChange & children_change)105 void MathMLOperatorElement::ChildrenChanged(
106 const ChildrenChange& children_change) {
107 operator_content_ = base::nullopt;
108 properties_.dictionary_category =
109 MathMLOperatorDictionaryCategory::kUndefined;
110 properties_.dirty_flags = kOperatorPropertyFlagsAll;
111 MathMLElement::ChildrenChanged(children_change);
112 }
113
SetOperatorPropertyDirtyFlagIfNeeded(const AttributeModificationParams & param,const OperatorPropertyFlag & flag,bool & needs_layout)114 void MathMLOperatorElement::SetOperatorPropertyDirtyFlagIfNeeded(
115 const AttributeModificationParams& param,
116 const OperatorPropertyFlag& flag,
117 bool& needs_layout) {
118 needs_layout = param.new_value != param.old_value;
119 if (needs_layout)
120 properties_.dirty_flags |= flag;
121 }
122
ParseAttribute(const AttributeModificationParams & param)123 void MathMLOperatorElement::ParseAttribute(
124 const AttributeModificationParams& param) {
125 bool needs_layout = false;
126 if (param.name == mathml_names::kFormAttr) {
127 needs_layout = param.new_value != param.old_value;
128 if (needs_layout) {
129 SetOperatorFormDirty();
130 properties_.dirty_flags |= kOperatorPropertyFlagsAll;
131 }
132 } else if (param.name == mathml_names::kStretchyAttr) {
133 SetOperatorPropertyDirtyFlagIfNeeded(
134 param, MathMLOperatorElement::kStretchy, needs_layout);
135 } else if (param.name == mathml_names::kSymmetricAttr) {
136 SetOperatorPropertyDirtyFlagIfNeeded(
137 param, MathMLOperatorElement::kSymmetric, needs_layout);
138 } else if (param.name == mathml_names::kLargeopAttr) {
139 SetOperatorPropertyDirtyFlagIfNeeded(param, MathMLOperatorElement::kLargeOp,
140 needs_layout);
141 } else if (param.name == mathml_names::kMovablelimitsAttr) {
142 SetOperatorPropertyDirtyFlagIfNeeded(
143 param, MathMLOperatorElement::kMovableLimits, needs_layout);
144 } else if (param.name == mathml_names::kLspaceAttr ||
145 param.name == mathml_names::kRspaceAttr) {
146 needs_layout = param.new_value != param.old_value;
147 if (needs_layout && GetLayoutObject()) {
148 SetNeedsStyleRecalc(
149 kLocalStyleChange,
150 StyleChangeReasonForTracing::Create(style_change_reason::kAttribute));
151 }
152 }
153 if (needs_layout && GetLayoutObject() && GetLayoutObject()->IsMathML()) {
154 GetLayoutObject()
155 ->SetNeedsLayoutAndIntrinsicWidthsRecalcAndFullPaintInvalidation(
156 layout_invalidation_reason::kAttributeChanged);
157 }
158 MathMLElement::ParseAttribute(param);
159 }
160
161 // https://mathml-refresh.github.io/mathml-core/#dfn-algorithm-for-determining-the-properties-of-an-embellished-operator
ComputeDictionaryCategory()162 void MathMLOperatorElement::ComputeDictionaryCategory() {
163 if (properties_.dictionary_category !=
164 MathMLOperatorDictionaryCategory::kUndefined)
165 return;
166 if (GetOperatorContent().characters.IsEmpty()) {
167 properties_.dictionary_category = MathMLOperatorDictionaryCategory::kNone;
168 return;
169 }
170
171 // We first determine the form attribute and use the default spacing and
172 // properties.
173 // https://mathml-refresh.github.io/mathml-core/#dfn-form
174 const auto& value = FastGetAttribute(mathml_names::kFormAttr);
175 bool explicit_form = true;
176 MathMLOperatorDictionaryForm form;
177 if (EqualIgnoringASCIICase(value, "prefix")) {
178 form = MathMLOperatorDictionaryForm::kPrefix;
179 } else if (EqualIgnoringASCIICase(value, "infix")) {
180 form = MathMLOperatorDictionaryForm::kInfix;
181 } else if (EqualIgnoringASCIICase(value, "postfix")) {
182 form = MathMLOperatorDictionaryForm::kPostfix;
183 } else {
184 // TODO(crbug.com/1121113): Implement the remaining rules for determining
185 // form.
186 // https://mathml-refresh.github.io/mathml-core/#dfn-algorithm-for-determining-the-form-of-an-embellished-operator
187 explicit_form = false;
188 if (!previousSibling() && nextSibling())
189 form = MathMLOperatorDictionaryForm::kPrefix;
190 else if (previousSibling() && !nextSibling())
191 form = MathMLOperatorDictionaryForm::kPostfix;
192 else
193 form = MathMLOperatorDictionaryForm::kInfix;
194 }
195
196 // We then try and find an entry in the operator dictionary to override the
197 // default values.
198 // https://mathml-refresh.github.io/mathml-core/#dfn-algorithm-for-determining-the-properties-of-an-embellished-operator
199 auto category = FindCategory(GetOperatorContent().characters, form);
200 if (category != MathMLOperatorDictionaryCategory::kNone) {
201 // Step 2.
202 properties_.dictionary_category = category;
203 } else {
204 if (!explicit_form) {
205 // Step 3.
206 for (uint8_t fallback_form = MathMLOperatorDictionaryForm::kInfix;
207 fallback_form <= MathMLOperatorDictionaryForm::kPostfix;
208 fallback_form++) {
209 if (fallback_form == form)
210 continue;
211 category = FindCategory(
212 GetOperatorContent().characters,
213 static_cast<MathMLOperatorDictionaryForm>(fallback_form));
214 if (category != MathMLOperatorDictionaryCategory::kNone) {
215 properties_.dictionary_category = category;
216 return;
217 }
218 }
219 }
220 // Step 4.
221 properties_.dictionary_category = MathMLOperatorDictionaryCategory::kNone;
222 }
223 }
224
ComputeOperatorProperty(OperatorPropertyFlag flag)225 void MathMLOperatorElement::ComputeOperatorProperty(OperatorPropertyFlag flag) {
226 DCHECK(properties_.dirty_flags & flag);
227 const auto& name = OperatorPropertyFlagToAttributeName(flag);
228 if (base::Optional<bool> value = BooleanAttribute(name)) {
229 // https://mathml-refresh.github.io/mathml-core/#dfn-algorithm-for-determining-the-properties-of-an-embellished-operator
230 // Step 1.
231 if (*value) {
232 properties_.flags |= flag;
233 } else {
234 properties_.flags &= ~flag;
235 }
236 } else {
237 // By default, the value specified in the operator dictionary are used.
238 ComputeDictionaryCategory();
239 DCHECK(properties_.dictionary_category !=
240 MathMLOperatorDictionaryCategory::kUndefined);
241 if (MathMLOperatorDictionaryCategories
242 [std::underlying_type_t<MathMLOperatorDictionaryCategory>(
243 properties_.dictionary_category)]
244 .flags &
245 flag) {
246 properties_.flags |= flag;
247 } else {
248 properties_.flags &= ~flag;
249 }
250 }
251 }
252
253 const MathMLOperatorElement::OperatorContent&
GetOperatorContent()254 MathMLOperatorElement::GetOperatorContent() {
255 if (!operator_content_)
256 operator_content_ = ParseOperatorContent();
257 return operator_content_.value();
258 }
259
HasBooleanProperty(OperatorPropertyFlag flag)260 bool MathMLOperatorElement::HasBooleanProperty(OperatorPropertyFlag flag) {
261 if (properties_.dirty_flags & flag) {
262 ComputeOperatorProperty(flag);
263 properties_.dirty_flags &= ~flag;
264 }
265 return properties_.flags & flag;
266 }
267
CheckFormAfterSiblingChange()268 void MathMLOperatorElement::CheckFormAfterSiblingChange() {
269 if (properties_.dictionary_category !=
270 MathMLOperatorDictionaryCategory::kUndefined &&
271 !FastHasAttribute(mathml_names::kFormAttr))
272 SetOperatorFormDirty();
273 }
274
SetOperatorFormDirty()275 void MathMLOperatorElement::SetOperatorFormDirty() {
276 properties_.dictionary_category =
277 MathMLOperatorDictionaryCategory::kUndefined;
278 }
279
AddMathLSpaceIfNeeded(ComputedStyle & style,const CSSToLengthConversionData & conversion_data)280 void MathMLOperatorElement::AddMathLSpaceIfNeeded(
281 ComputedStyle& style,
282 const CSSToLengthConversionData& conversion_data) {
283 if (auto length_or_percentage_value = AddMathLengthToComputedStyle(
284 conversion_data, mathml_names::kLspaceAttr)) {
285 style.SetMathLSpace(std::move(*length_or_percentage_value));
286 }
287 }
288
AddMathRSpaceIfNeeded(ComputedStyle & style,const CSSToLengthConversionData & conversion_data)289 void MathMLOperatorElement::AddMathRSpaceIfNeeded(
290 ComputedStyle& style,
291 const CSSToLengthConversionData& conversion_data) {
292 if (auto length_or_percentage_value = AddMathLengthToComputedStyle(
293 conversion_data, mathml_names::kRspaceAttr)) {
294 style.SetMathRSpace(std::move(*length_or_percentage_value));
295 }
296 }
297
AddMathMinSizeIfNeeded(ComputedStyle & style,const CSSToLengthConversionData & conversion_data)298 void MathMLOperatorElement::AddMathMinSizeIfNeeded(
299 ComputedStyle& style,
300 const CSSToLengthConversionData& conversion_data) {
301 if (auto length_or_percentage_value = AddMathLengthToComputedStyle(
302 conversion_data, mathml_names::kMinsizeAttr)) {
303 style.SetMathMinSize(std::move(*length_or_percentage_value));
304 }
305 }
306
AddMathMaxSizeIfNeeded(ComputedStyle & style,const CSSToLengthConversionData & conversion_data)307 void MathMLOperatorElement::AddMathMaxSizeIfNeeded(
308 ComputedStyle& style,
309 const CSSToLengthConversionData& conversion_data) {
310 if (auto length_or_percentage_value = AddMathLengthToComputedStyle(
311 conversion_data, mathml_names::kMaxsizeAttr)) {
312 style.SetMathMaxSize(std::move(*length_or_percentage_value));
313 }
314 }
315
DefaultLeadingSpace()316 double MathMLOperatorElement::DefaultLeadingSpace() {
317 ComputeDictionaryCategory();
318 return static_cast<float>(
319 MathMLOperatorDictionaryCategories
320 [std::underlying_type_t<MathMLOperatorDictionaryCategory>(
321 properties_.dictionary_category)]
322 .leading_space_in_math_unit) *
323 kMathUnitFraction;
324 }
325
DefaultTrailingSpace()326 double MathMLOperatorElement::DefaultTrailingSpace() {
327 ComputeDictionaryCategory();
328 return static_cast<float>(
329 MathMLOperatorDictionaryCategories
330 [std::underlying_type_t<MathMLOperatorDictionaryCategory>(
331 properties_.dictionary_category)]
332 .trailing_space_in_math_unit) *
333 kMathUnitFraction;
334 }
335
336 } // namespace blink
337