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