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/platform/fonts/opentype/open_type_math_support.h"
6 #include "base/memory/scoped_refptr.h"
7 #include "testing/gtest/include/gtest/gtest.h"
8 #include "third_party/blink/renderer/platform/fonts/font.h"
9 #include "third_party/blink/renderer/platform/fonts/opentype/open_type_types.h"
10 #include "third_party/blink/renderer/platform/testing/font_test_helpers.h"
11 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
12
13 namespace {
14 const UChar32 kLeftBraceCodePoint = '{';
15 const UChar32 kOverBraceCodePoint = 0x23DE;
16 const UChar32 kRightwardsFrontTiltedShadowedWhiteArrowCodePoint = 0x1F8AB;
17 const UChar32 kNAryWhiteVerticalBarCodePoint = 0x2AFF;
18 } // namespace
19
20 namespace blink {
21
22 class OpenTypeMathSupportTest : public testing::Test {
23 protected:
SetUp()24 void SetUp() override {
25 font_description.SetComputedSize(10.0);
26 font = Font(font_description);
27 }
28
TearDown()29 void TearDown() override {}
30
CreateMathFont(const String & name,float size=1000)31 Font CreateMathFont(const String& name, float size = 1000) {
32 FontDescription::VariantLigatures ligatures;
33 return blink::test::CreateTestFont(
34 "MathTestFont",
35 blink::test::BlinkWebTestsFontsTestDataPath(String("math/") + name),
36 size, &ligatures);
37 }
38
HasMathData(const String & name)39 bool HasMathData(const String& name) {
40 return OpenTypeMathSupport::HasMathData(
41 CreateMathFont(name).PrimaryFont()->PlatformData().GetHarfBuzzFace());
42 }
43
MathConstant(const String & name,OpenTypeMathSupport::MathConstants constant)44 base::Optional<float> MathConstant(
45 const String& name,
46 OpenTypeMathSupport::MathConstants constant) {
47 Font math = CreateMathFont(name);
48 return OpenTypeMathSupport::MathConstant(
49 math.PrimaryFont()->PlatformData().GetHarfBuzzFace(), constant);
50 }
51
52 FontDescription font_description;
53 Font font;
54 };
55
TEST_F(OpenTypeMathSupportTest,HasMathData)56 TEST_F(OpenTypeMathSupportTest, HasMathData) {
57 // Null parameter.
58 EXPECT_FALSE(OpenTypeMathSupport::HasMathData(nullptr));
59
60 // Font without a MATH table.
61 EXPECT_FALSE(HasMathData("math-text.woff"));
62
63 // Font with a MATH table.
64 EXPECT_TRUE(HasMathData("axisheight5000-verticalarrow14000.woff"));
65 }
66
TEST_F(OpenTypeMathSupportTest,MathConstantNullOpt)67 TEST_F(OpenTypeMathSupportTest, MathConstantNullOpt) {
68 Font math_text = CreateMathFont("math-text.woff");
69
70 for (int i = OpenTypeMathSupport::MathConstants::kScriptPercentScaleDown;
71 i <=
72 OpenTypeMathSupport::MathConstants::kRadicalDegreeBottomRaisePercent;
73 i++) {
74 auto math_constant = static_cast<OpenTypeMathSupport::MathConstants>(i);
75
76 // Null parameter.
77 EXPECT_FALSE(OpenTypeMathSupport::MathConstant(nullptr, math_constant));
78
79 // Font without a MATH table.
80 EXPECT_FALSE(OpenTypeMathSupport::MathConstant(
81 math_text.PrimaryFont()->PlatformData().GetHarfBuzzFace(),
82 math_constant));
83 }
84 }
85
86 // See third_party/blink/web_tests/external/wpt/mathml/tools/percentscaledown.py
TEST_F(OpenTypeMathSupportTest,MathConstantPercentScaleDown)87 TEST_F(OpenTypeMathSupportTest, MathConstantPercentScaleDown) {
88 {
89 auto result = MathConstant(
90 "scriptpercentscaledown80-scriptscriptpercentscaledown0.woff",
91 OpenTypeMathSupport::MathConstants::kScriptPercentScaleDown);
92 EXPECT_TRUE(result);
93 EXPECT_FLOAT_EQ(*result, .8);
94 }
95
96 {
97 auto result = MathConstant(
98 "scriptpercentscaledown0-scriptscriptpercentscaledown40.woff",
99 OpenTypeMathSupport::MathConstants::kScriptScriptPercentScaleDown);
100 EXPECT_TRUE(result);
101 EXPECT_FLOAT_EQ(*result, .4);
102 }
103 }
104
105 // See third_party/blink/web_tests/external/wpt/mathml/tools/fractions.py
TEST_F(OpenTypeMathSupportTest,MathConstantFractions)106 TEST_F(OpenTypeMathSupportTest, MathConstantFractions) {
107 {
108 auto result = MathConstant(
109 "fraction-numeratorshiftup11000-axisheight1000-rulethickness1000.woff",
110 OpenTypeMathSupport::MathConstants::kFractionNumeratorShiftUp);
111 EXPECT_TRUE(result);
112 EXPECT_FLOAT_EQ(*result, 11000);
113 }
114
115 {
116 auto result = MathConstant(
117 "fraction-numeratordisplaystyleshiftup2000-axisheight1000-"
118 "rulethickness1000.woff",
119 OpenTypeMathSupport::MathConstants::
120 kFractionNumeratorDisplayStyleShiftUp);
121 EXPECT_TRUE(result);
122 EXPECT_FLOAT_EQ(*result, 2000);
123 }
124
125 {
126 auto result = MathConstant(
127 "fraction-denominatorshiftdown3000-axisheight1000-rulethickness1000."
128 "woff",
129 OpenTypeMathSupport::MathConstants::kFractionDenominatorShiftDown);
130 EXPECT_TRUE(result);
131 EXPECT_FLOAT_EQ(*result, 3000);
132 }
133
134 {
135 auto result = MathConstant(
136 "fraction-denominatordisplaystyleshiftdown6000-axisheight1000-"
137 "rulethickness1000.woff",
138 OpenTypeMathSupport::MathConstants::
139 kFractionDenominatorDisplayStyleShiftDown);
140 EXPECT_TRUE(result);
141 EXPECT_FLOAT_EQ(*result, 6000);
142 }
143
144 {
145 auto result = MathConstant(
146 "fraction-numeratorgapmin9000-rulethickness1000.woff",
147 OpenTypeMathSupport::MathConstants::kFractionNumeratorGapMin);
148 EXPECT_TRUE(result);
149 EXPECT_FLOAT_EQ(*result, 9000);
150 }
151
152 {
153 auto result = MathConstant(
154 "fraction-numeratordisplaystylegapmin8000-rulethickness1000.woff",
155 OpenTypeMathSupport::MathConstants::kFractionNumDisplayStyleGapMin);
156 EXPECT_TRUE(result);
157 EXPECT_FLOAT_EQ(*result, 8000);
158 }
159
160 {
161 auto result = MathConstant(
162 "fraction-rulethickness10000.woff",
163 OpenTypeMathSupport::MathConstants::kFractionRuleThickness);
164 EXPECT_TRUE(result);
165 EXPECT_FLOAT_EQ(*result, 10000);
166 }
167
168 {
169 auto result = MathConstant(
170 "fraction-denominatorgapmin4000-rulethickness1000.woff",
171 OpenTypeMathSupport::MathConstants::kFractionDenominatorGapMin);
172 EXPECT_TRUE(result);
173 EXPECT_FLOAT_EQ(*result, 4000);
174 }
175
176 {
177 auto result = MathConstant(
178 "fraction-denominatordisplaystylegapmin5000-rulethickness1000.woff",
179 OpenTypeMathSupport::MathConstants::kFractionDenomDisplayStyleGapMin);
180 EXPECT_TRUE(result);
181 EXPECT_FLOAT_EQ(*result, 5000);
182 }
183 }
184
185 // See third_party/blink/web_tests/external/wpt/mathml/tools/radicals.py
TEST_F(OpenTypeMathSupportTest,MathConstantRadicals)186 TEST_F(OpenTypeMathSupportTest, MathConstantRadicals) {
187 {
188 auto result = MathConstant(
189 "radical-degreebottomraisepercent25-rulethickness1000.woff",
190 OpenTypeMathSupport::MathConstants::kRadicalDegreeBottomRaisePercent);
191 EXPECT_TRUE(result);
192 EXPECT_FLOAT_EQ(*result, .25);
193 }
194
195 {
196 auto result =
197 MathConstant("radical-verticalgap6000-rulethickness1000.woff",
198 OpenTypeMathSupport::MathConstants::kRadicalVerticalGap);
199 EXPECT_TRUE(result);
200 EXPECT_FLOAT_EQ(*result, 6000);
201 }
202
203 {
204 auto result = MathConstant(
205 "radical-displaystyleverticalgap7000-rulethickness1000.woff",
206 OpenTypeMathSupport::MathConstants::kRadicalDisplayStyleVerticalGap);
207 EXPECT_TRUE(result);
208 EXPECT_FLOAT_EQ(*result, 7000);
209 }
210
211 {
212 auto result =
213 MathConstant("radical-rulethickness8000.woff",
214 OpenTypeMathSupport::MathConstants::kRadicalRuleThickness);
215 EXPECT_TRUE(result);
216 EXPECT_FLOAT_EQ(*result, 8000);
217 }
218
219 {
220 auto result =
221 MathConstant("radical-extraascender3000-rulethickness1000.woff",
222 OpenTypeMathSupport::MathConstants::kRadicalExtraAscender);
223 EXPECT_TRUE(result);
224 EXPECT_FLOAT_EQ(*result, 3000);
225 }
226
227 {
228 auto result = MathConstant(
229 "radical-kernbeforedegree4000-rulethickness1000.woff",
230 OpenTypeMathSupport::MathConstants::kRadicalKernBeforeDegree);
231 EXPECT_TRUE(result);
232 EXPECT_FLOAT_EQ(*result, 4000);
233 }
234
235 {
236 auto result = MathConstant(
237 "radical-kernafterdegreeminus5000-rulethickness1000.woff",
238 OpenTypeMathSupport::MathConstants::kRadicalKernAfterDegree);
239 EXPECT_TRUE(result);
240 EXPECT_FLOAT_EQ(*result, -5000);
241 }
242 }
243
TEST_F(OpenTypeMathSupportTest,MathVariantsWithoutTable)244 TEST_F(OpenTypeMathSupportTest, MathVariantsWithoutTable) {
245 Font math = CreateMathFont("math-text.woff");
246 auto glyph = math.PrimaryFont()->GlyphForCharacter('A');
247
248 // Horizontal variants.
249 {
250 auto variants = OpenTypeMathSupport::GetGlyphVariantRecords(
251 math.PrimaryFont()->PlatformData().GetHarfBuzzFace(), glyph,
252 OpenTypeMathStretchData::StretchAxis::Horizontal);
253 EXPECT_EQ(variants.size(), 1u);
254 EXPECT_EQ(variants[0], glyph);
255 }
256
257 // Vertical variants.
258 {
259 auto variants = OpenTypeMathSupport::GetGlyphVariantRecords(
260 math.PrimaryFont()->PlatformData().GetHarfBuzzFace(), glyph,
261 OpenTypeMathStretchData::StretchAxis::Vertical);
262 EXPECT_EQ(variants.size(), 1u);
263 EXPECT_EQ(variants[0], glyph);
264 }
265
266 // Horizontal parts.
267 {
268 auto parts = OpenTypeMathSupport::GetGlyphPartRecords(
269 math.PrimaryFont()->PlatformData().GetHarfBuzzFace(), glyph,
270 OpenTypeMathStretchData::StretchAxis::Horizontal);
271 EXPECT_TRUE(parts.IsEmpty());
272 }
273
274 // // Vertical parts.
275 {
276 auto parts = OpenTypeMathSupport::GetGlyphPartRecords(
277 math.PrimaryFont()->PlatformData().GetHarfBuzzFace(), glyph,
278 OpenTypeMathStretchData::StretchAxis::Vertical);
279 EXPECT_TRUE(parts.IsEmpty());
280 }
281 }
282
TEST_F(OpenTypeMathSupportTest,MathVariantsWithTable)283 TEST_F(OpenTypeMathSupportTest, MathVariantsWithTable) {
284 // operators.woff contains stretchy operators from the MathML operator
285 // dictionary (including left and over braces) represented by squares.
286 // It also contains glyphs h0, h1, h2, h3 and v0, v1, v2, v3 that are
287 // respectively horizontal and vertical rectangles of increasing size.
288 // The MathVariants table contains the following data for horizontal
289 // (respectively vertical) operators:
290 // - Glyph variants: h0, h1, h2, h3 (respectively v0, v1, v2, v3).
291 // - Glyph parts: non-extender h2 and extender h1 (respectively v2 and v1).
292 // For details, see createSizeVariants() and createStretchy() from
293 // third_party/blink/web_tests/external/wpt/mathml/tools/operator-dictionary.py
294
295 Font math = CreateMathFont("operators.woff");
296 auto left_brace = math.PrimaryFont()->GlyphForCharacter(kLeftBraceCodePoint);
297 auto over_brace = math.PrimaryFont()->GlyphForCharacter(kOverBraceCodePoint);
298
299 // Calculate glyph indices from the last unicode character in the font.
300 // TODO(https://crbug.com/1057596): Find a better way to access these glyph
301 // indices.
302 auto v0 = math.PrimaryFont()->GlyphForCharacter(
303 kRightwardsFrontTiltedShadowedWhiteArrowCodePoint) +
304 1;
305 auto h0 = v0 + 1;
306 auto v1 = h0 + 1;
307 auto h1 = v1 + 1;
308 auto v2 = h1 + 1;
309 auto h2 = v2 + 1;
310 auto v3 = h2 + 1;
311 auto h3 = v3 + 1;
312
313 // Vertical variants for vertical operator.
314 {
315 auto variants = OpenTypeMathSupport::GetGlyphVariantRecords(
316 math.PrimaryFont()->PlatformData().GetHarfBuzzFace(), left_brace,
317 OpenTypeMathStretchData::StretchAxis::Vertical);
318 EXPECT_EQ(variants.size(), 5u);
319 EXPECT_EQ(variants[0], left_brace);
320 EXPECT_EQ(variants[1], v0);
321 EXPECT_EQ(variants[2], v1);
322 EXPECT_EQ(variants[3], v2);
323 EXPECT_EQ(variants[4], v3);
324 }
325
326 // Horizontal variants for vertical operator.
327 {
328 auto variants = OpenTypeMathSupport::GetGlyphVariantRecords(
329 math.PrimaryFont()->PlatformData().GetHarfBuzzFace(), left_brace,
330 OpenTypeMathStretchData::StretchAxis::Horizontal);
331 EXPECT_EQ(variants.size(), 1u);
332 EXPECT_EQ(variants[0], left_brace);
333 }
334
335 // Horizontal variants for horizontal operator.
336 {
337 auto variants = OpenTypeMathSupport::GetGlyphVariantRecords(
338 math.PrimaryFont()->PlatformData().GetHarfBuzzFace(), over_brace,
339 OpenTypeMathStretchData::StretchAxis::Horizontal);
340 EXPECT_EQ(variants.size(), 5u);
341 EXPECT_EQ(variants[0], over_brace);
342 EXPECT_EQ(variants[1], h0);
343 EXPECT_EQ(variants[2], h1);
344 EXPECT_EQ(variants[3], h2);
345 EXPECT_EQ(variants[4], h3);
346 }
347
348 // Vertical variants for horizontal operator.
349 {
350 auto variants = OpenTypeMathSupport::GetGlyphVariantRecords(
351 math.PrimaryFont()->PlatformData().GetHarfBuzzFace(), over_brace,
352 OpenTypeMathStretchData::StretchAxis::Vertical);
353 EXPECT_EQ(variants.size(), 1u);
354 EXPECT_EQ(variants[0], over_brace);
355 }
356
357 // Vertical parts for vertical operator.
358 {
359 auto parts = OpenTypeMathSupport::GetGlyphPartRecords(
360 math.PrimaryFont()->PlatformData().GetHarfBuzzFace(), left_brace,
361 OpenTypeMathStretchData::StretchAxis::Vertical);
362 EXPECT_EQ(parts.size(), 2u);
363 EXPECT_EQ(parts[0].glyph, v2);
364 EXPECT_FLOAT_EQ(parts[0].start_connector_length, 0);
365 EXPECT_FLOAT_EQ(parts[0].end_connector_length, 1000);
366 EXPECT_FLOAT_EQ(parts[0].full_advance, 3000);
367 EXPECT_EQ(parts[0].is_extender, false);
368 EXPECT_EQ(parts[1].glyph, v1);
369 EXPECT_FLOAT_EQ(parts[1].start_connector_length, 1000);
370 EXPECT_FLOAT_EQ(parts[1].end_connector_length, 1000);
371 EXPECT_FLOAT_EQ(parts[1].full_advance, 2000);
372 EXPECT_EQ(parts[1].is_extender, true);
373 }
374
375 // Horizontal parts for vertical operator.
376 {
377 auto parts = OpenTypeMathSupport::GetGlyphPartRecords(
378 math.PrimaryFont()->PlatformData().GetHarfBuzzFace(), left_brace,
379 OpenTypeMathStretchData::StretchAxis::Horizontal);
380 EXPECT_TRUE(parts.IsEmpty());
381 }
382
383 // Horizontal parts for horizontal operator.
384 {
385 auto parts = OpenTypeMathSupport::GetGlyphPartRecords(
386 math.PrimaryFont()->PlatformData().GetHarfBuzzFace(), over_brace,
387 OpenTypeMathStretchData::StretchAxis::Horizontal);
388
389 EXPECT_EQ(parts.size(), 2u);
390 EXPECT_EQ(parts[0].glyph, h2);
391 EXPECT_FLOAT_EQ(parts[0].start_connector_length, 0);
392 EXPECT_FLOAT_EQ(parts[0].end_connector_length, 1000);
393 EXPECT_FLOAT_EQ(parts[0].full_advance, 3000);
394 EXPECT_EQ(parts[0].is_extender, false);
395
396 EXPECT_EQ(parts[1].glyph, h1);
397 EXPECT_FLOAT_EQ(parts[1].start_connector_length, 1000);
398 EXPECT_FLOAT_EQ(parts[1].end_connector_length, 1000);
399 EXPECT_FLOAT_EQ(parts[1].full_advance, 2000);
400 EXPECT_EQ(parts[1].is_extender, true);
401 }
402
403 // Vertical parts for horizontal operator.
404 {
405 auto parts = OpenTypeMathSupport::GetGlyphPartRecords(
406 math.PrimaryFont()->PlatformData().GetHarfBuzzFace(), over_brace,
407 OpenTypeMathStretchData::StretchAxis::Vertical);
408 EXPECT_TRUE(parts.IsEmpty());
409 }
410 }
411
412 // See third_party/blink/web_tests/external/wpt/mathml/tools/largeop.py
TEST_F(OpenTypeMathSupportTest,MathItalicCorrection)413 TEST_F(OpenTypeMathSupportTest, MathItalicCorrection) {
414 {
415 Font math = CreateMathFont(
416 "largeop-displayoperatorminheight2000-2AFF-italiccorrection3000.woff");
417 Glyph base_glyph =
418 math.PrimaryFont()->GlyphForCharacter(kNAryWhiteVerticalBarCodePoint);
419
420 // Retrieve the glyph with italic correction.
421 Vector<OpenTypeMathStretchData::GlyphVariantRecord> variants =
422 OpenTypeMathSupport::GetGlyphVariantRecords(
423 math.PrimaryFont()->PlatformData().GetHarfBuzzFace(), base_glyph,
424 OpenTypeMathStretchData::StretchAxis::Vertical);
425 EXPECT_EQ(variants.size(), 3u);
426 EXPECT_EQ(variants[0], base_glyph);
427 EXPECT_EQ(variants[1], base_glyph);
428 Glyph glyph_with_italic_correction = variants[2];
429
430 // MathItalicCorrection with a value.
431 base::Optional<float> glyph_with_italic_correction_value =
432 OpenTypeMathSupport::MathItalicCorrection(
433 math.PrimaryFont()->PlatformData().GetHarfBuzzFace(),
434 glyph_with_italic_correction);
435 EXPECT_TRUE(glyph_with_italic_correction_value);
436 EXPECT_FLOAT_EQ(*glyph_with_italic_correction_value, 3000);
437
438 // GetGlyphPartRecords does not set italic correction when there is no
439 // construction available.
440 float italic_correction = -1000;
441 Vector<OpenTypeMathStretchData::GlyphPartRecord> parts =
442 OpenTypeMathSupport::GetGlyphPartRecords(
443 math.PrimaryFont()->PlatformData().GetHarfBuzzFace(), base_glyph,
444 OpenTypeMathStretchData::StretchAxis::Vertical, &italic_correction);
445 EXPECT_TRUE(parts.IsEmpty());
446 EXPECT_FLOAT_EQ(italic_correction, -1000);
447 }
448
449 {
450 Font math = CreateMathFont(
451 "largeop-displayoperatorminheight7000-2AFF-italiccorrection5000.woff");
452 Glyph base_glyph =
453 math.PrimaryFont()->GlyphForCharacter(kNAryWhiteVerticalBarCodePoint);
454
455 // OpenTypeMathSupport::GetGlyphPartRecords sets italic correction.
456 float italic_correction = -1000;
457 Vector<OpenTypeMathStretchData::GlyphPartRecord> parts =
458 OpenTypeMathSupport::GetGlyphPartRecords(
459 math.PrimaryFont()->PlatformData().GetHarfBuzzFace(), base_glyph,
460 OpenTypeMathStretchData::StretchAxis::Vertical, &italic_correction);
461 EXPECT_EQ(parts.size(), 3u);
462 EXPECT_FLOAT_EQ(italic_correction, 5000);
463 }
464 }
465
466 } // namespace blink
467