1/*
2 * Copyright (C) 2016 Igalia S.L. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
14 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
15 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
16 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
17 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
19 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 *
25 */
26
27#include "config.h"
28#include "MathMLOperatorElement.h"
29
30#if ENABLE(MATHML)
31
32#include "RenderMathMLOperator.h"
33#include <wtf/IsoMallocInlines.h>
34#include <wtf/unicode/CharacterNames.h>
35
36namespace WebCore {
37
38WTF_MAKE_ISO_ALLOCATED_IMPL(MathMLOperatorElement);
39
40using namespace MathMLNames;
41using namespace MathMLOperatorDictionary;
42
43MathMLOperatorElement::MathMLOperatorElement(const QualifiedName& tagName, Document& document)
44 : MathMLTokenElement(tagName, document)
45{
46}
47
48Ref<MathMLOperatorElement> MathMLOperatorElement::create(const QualifiedName& tagName, Document& document)
49{
50 return adoptRef(*new MathMLOperatorElement(tagName, document));
51}
52
53MathMLOperatorElement::OperatorChar MathMLOperatorElement::parseOperatorChar(const String& string)
54{
55 OperatorChar operatorChar;
56 // FIXME: This operator dictionary does not accept multiple characters (https://webkit.org/b/124828).
57 if (auto codePoint = convertToSingleCodePoint(string)) {
58 auto character = codePoint.value();
59 // The minus sign renders better than the hyphen sign used in some MathML formulas.
60 if (character == hyphenMinus)
61 character = minusSign;
62 operatorChar.character = character;
63 operatorChar.isVertical = isVertical(operatorChar.character);
64 }
65 return operatorChar;
66}
67
68const MathMLOperatorElement::OperatorChar& MathMLOperatorElement::operatorChar()
69{
70 if (!m_operatorChar)
71 m_operatorChar = parseOperatorChar(textContent());
72 return m_operatorChar.value();
73}
74
75Property MathMLOperatorElement::computeDictionaryProperty()
76{
77 Property dictionaryProperty;
78
79 // We first determine the form attribute and use the default spacing and properties.
80 const auto& value = attributeWithoutSynchronization(formAttr);
81 bool explicitForm = true;
82 if (value == "prefix")
83 dictionaryProperty.form = Prefix;
84 else if (value == "infix")
85 dictionaryProperty.form = Infix;
86 else if (value == "postfix")
87 dictionaryProperty.form = Postfix;
88 else {
89 // FIXME: We should use more advanced heuristics indicated in the specification to determine the operator form (https://bugs.webkit.org/show_bug.cgi?id=124829).
90 explicitForm = false;
91 if (!previousSibling() && nextSibling())
92 dictionaryProperty.form = Prefix;
93 else if (previousSibling() && !nextSibling())
94 dictionaryProperty.form = Postfix;
95 else
96 dictionaryProperty.form = Infix;
97 }
98
99 // We then try and find an entry in the operator dictionary to override the default values.
100 if (auto entry = search(operatorChar().character, dictionaryProperty.form, explicitForm))
101 dictionaryProperty = entry.value();
102
103 return dictionaryProperty;
104}
105
106const Property& MathMLOperatorElement::dictionaryProperty()
107{
108 if (!m_dictionaryProperty)
109 m_dictionaryProperty = computeDictionaryProperty();
110 return m_dictionaryProperty.value();
111}
112
113static const QualifiedName& propertyFlagToAttributeName(MathMLOperatorDictionary::Flag flag)
114{
115 switch (flag) {
116 case Accent:
117 return accentAttr;
118 case Fence:
119 return fenceAttr;
120 case LargeOp:
121 return largeopAttr;
122 case MovableLimits:
123 return movablelimitsAttr;
124 case Separator:
125 return separatorAttr;
126 case Stretchy:
127 return stretchyAttr;
128 case Symmetric:
129 return symmetricAttr;
130 }
131 ASSERT_NOT_REACHED();
132 return nullQName();
133}
134
135void MathMLOperatorElement::computeOperatorFlag(MathMLOperatorDictionary::Flag flag)
136{
137 ASSERT(m_properties.dirtyFlags & flag);
138
139 Optional<BooleanValue> property;
140 const auto& name = propertyFlagToAttributeName(flag);
141 const BooleanValue& value = cachedBooleanAttribute(name, property);
142 switch (value) {
143 case BooleanValue::True:
144 m_properties.flags |= flag;
145 break;
146 case BooleanValue::False:
147 m_properties.flags &= ~flag;
148 break;
149 case BooleanValue::Default:
150 // By default, we use the value specified in the operator dictionary.
151 if (dictionaryProperty().flags & flag)
152 m_properties.flags |= flag;
153 else
154 m_properties.flags &= ~flag;
155 break;
156 }
157}
158
159bool MathMLOperatorElement::hasProperty(MathMLOperatorDictionary::Flag flag)
160{
161 if (m_properties.dirtyFlags & flag) {
162 computeOperatorFlag(flag);
163 m_properties.dirtyFlags &= ~flag;
164 }
165 return m_properties.flags & flag;
166}
167
168MathMLElement::Length MathMLOperatorElement::defaultLeadingSpace()
169{
170 Length space;
171 space.type = LengthType::MathUnit;
172 space.value = static_cast<float>(dictionaryProperty().leadingSpaceInMathUnit);
173 return space;
174}
175
176MathMLElement::Length MathMLOperatorElement::defaultTrailingSpace()
177{
178 Length space;
179 space.type = LengthType::MathUnit;
180 space.value = static_cast<float>(dictionaryProperty().trailingSpaceInMathUnit);
181 return space;
182}
183
184const MathMLElement::Length& MathMLOperatorElement::leadingSpace()
185{
186 return cachedMathMLLength(MathMLNames::lspaceAttr, m_leadingSpace);
187}
188
189const MathMLElement::Length& MathMLOperatorElement::trailingSpace()
190{
191 return cachedMathMLLength(MathMLNames::rspaceAttr, m_trailingSpace);
192}
193
194const MathMLElement::Length& MathMLOperatorElement::minSize()
195{
196 return cachedMathMLLength(MathMLNames::minsizeAttr, m_minSize);
197}
198
199const MathMLElement::Length& MathMLOperatorElement::maxSize()
200{
201 if (m_maxSize)
202 return m_maxSize.value();
203
204 const AtomicString& value = attributeWithoutSynchronization(MathMLNames::maxsizeAttr);
205 if (value == "infinity") {
206 Length maxsize;
207 maxsize.type = LengthType::Infinity;
208 m_maxSize = maxsize;
209 } else
210 m_maxSize = parseMathMLLength(value);
211
212 return m_maxSize.value();
213}
214
215void MathMLOperatorElement::childrenChanged(const ChildChange& change)
216{
217 m_operatorChar = WTF::nullopt;
218 m_dictionaryProperty = WTF::nullopt;
219 m_properties.dirtyFlags = MathMLOperatorDictionary::allFlags;
220 MathMLTokenElement::childrenChanged(change);
221}
222
223static Optional<MathMLOperatorDictionary::Flag> attributeNameToPropertyFlag(const QualifiedName& name)
224{
225 if (name == accentAttr)
226 return Accent;
227 if (name == fenceAttr)
228 return Fence;
229 if (name == largeopAttr)
230 return LargeOp;
231 if (name == movablelimitsAttr)
232 return MovableLimits;
233 if (name == separatorAttr)
234 return Separator;
235 if (name == stretchyAttr)
236 return Stretchy;
237 if (name == symmetricAttr)
238 return Symmetric;
239 return WTF::nullopt;
240}
241
242void MathMLOperatorElement::parseAttribute(const QualifiedName& name, const AtomicString& value)
243{
244 if (name == formAttr) {
245 m_dictionaryProperty = WTF::nullopt;
246 m_properties.dirtyFlags = MathMLOperatorDictionary::allFlags;
247 } else if (auto flag = attributeNameToPropertyFlag(name))
248 m_properties.dirtyFlags |= flag.value();
249 else if (name == lspaceAttr)
250 m_leadingSpace = WTF::nullopt;
251 else if (name == rspaceAttr)
252 m_trailingSpace = WTF::nullopt;
253 else if (name == minsizeAttr)
254 m_minSize = WTF::nullopt;
255 else if (name == maxsizeAttr)
256 m_maxSize = WTF::nullopt;
257
258 if ((name == stretchyAttr || name == lspaceAttr || name == rspaceAttr || name == movablelimitsAttr) && renderer()) {
259 downcast<RenderMathMLOperator>(*renderer()).updateFromElement();
260 return;
261 }
262
263 MathMLTokenElement::parseAttribute(name, value);
264}
265
266RenderPtr<RenderElement> MathMLOperatorElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
267{
268 ASSERT(hasTagName(MathMLNames::moTag));
269 return createRenderer<RenderMathMLOperator>(*this, WTFMove(style));
270}
271
272}
273
274#endif // ENABLE(MATHML)
275