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