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/shaping/stretchy_operator_shaper.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/fonts/shaping/shape_result_inline_headers.h"
11 #include "third_party/blink/renderer/platform/fonts/shaping/shape_result_test_info.h"
12 #include "third_party/blink/renderer/platform/testing/font_test_helpers.h"
13 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
14 
15 namespace blink {
16 
17 namespace {
18 
19 const UChar32 kLeftBraceCodePoint = '{';
20 const UChar32 kOverBraceCodePoint = 0x23DE;
21 const UChar32 kRightwardsFrontTiltedShadowedWhiteArrowCodePoint = 0x1F8AB;
22 const UChar32 kNAryWhiteVerticalBarCodePoint = 0x2AFF;
23 float kSizeError = .1;
24 
TestInfo(const scoped_refptr<ShapeResult> & result)25 ShapeResultTestInfo* TestInfo(const scoped_refptr<ShapeResult>& result) {
26   return static_cast<ShapeResultTestInfo*>(result.get());
27 }
28 
29 }  // namespace
30 
31 class StretchyOperatorShaperTest : public testing::Test {
32  protected:
SetUp()33   void SetUp() override {
34     font_description.SetComputedSize(10.0);
35     font = Font(font_description);
36   }
37 
TearDown()38   void TearDown() override {}
39 
CreateMathFont(const String & name,float size=1000)40   Font CreateMathFont(const String& name, float size = 1000) {
41     FontDescription::VariantLigatures ligatures;
42     return blink::test::CreateTestFont(
43         "MathTestFont",
44         blink::test::BlinkWebTestsFontsTestDataPath(String("math/") + name),
45         size, &ligatures);
46   }
47 
48   FontDescription font_description;
49   Font font;
50 };
51 
52 // See createStretchy() in
53 // third_party/blink/web_tests/external/wpt/mathml/tools/operator-dictionary.py
TEST_F(StretchyOperatorShaperTest,GlyphVariants)54 TEST_F(StretchyOperatorShaperTest, GlyphVariants) {
55   Font math = CreateMathFont("operators.woff");
56 
57   StretchyOperatorShaper vertical_shaper(
58       kLeftBraceCodePoint, OpenTypeMathStretchData::StretchAxis::Vertical);
59   StretchyOperatorShaper horizontal_shaper(
60       kOverBraceCodePoint, OpenTypeMathStretchData::StretchAxis::Horizontal);
61 
62   auto left_brace = math.PrimaryFont()->GlyphForCharacter(kLeftBraceCodePoint);
63   auto over_brace = math.PrimaryFont()->GlyphForCharacter(kOverBraceCodePoint);
64 
65   // Calculate glyph indices from the last unicode character in the font.
66   // TODO(https://crbug.com/1057596): Find a better way to access these glyph
67   // indices.
68   auto v0 = math.PrimaryFont()->GlyphForCharacter(
69                 kRightwardsFrontTiltedShadowedWhiteArrowCodePoint) +
70             1;
71   auto h0 = v0 + 1;
72   auto v1 = h0 + 1;
73   auto h1 = v1 + 1;
74   auto v2 = h1 + 1;
75   auto h2 = v2 + 1;
76 
77   // Stretch operators to target sizes (in font units) 125, 250, 375, 500, 625,
78   // 750, 875, 1000, 1125, ..., 3750, 3875, 4000.
79   //
80   // Shaper tries glyphs over_brace/left_brace, h0/v0, h1/v1, h2/v2, h3/v3 of
81   // respective sizes 1000, 1000, 2000, 3000 and 4000. It returns the smallest
82   // glyph larger than the target size.
83   const unsigned size_count = 4;
84   const unsigned subdivision = 8;
85   for (unsigned i = 0; i < size_count; i++) {
86     for (unsigned j = 1; j <= subdivision; j++) {
87       // Due to floating-point errors, the actual metrics of the size variants
88       // might actually be slightly smaller than expected. Reduce the
89       // target_size by kSizeError to ensure that the shaper picks the desired
90       // size variant.
91       float target_size = i * 1000 + (j * 1000 / subdivision) - kSizeError;
92 
93       // Metrics of horizontal size variants.
94       {
95         StretchyOperatorShaper::Metrics metrics;
96         horizontal_shaper.Shape(&math, target_size, &metrics);
97         EXPECT_NEAR(metrics.advance, (i + 1) * 1000, kSizeError);
98         EXPECT_NEAR(metrics.ascent, 1000, kSizeError);
99         EXPECT_FLOAT_EQ(metrics.descent, 0);
100       }
101 
102       // Metrics of vertical size variants.
103 
104       {
105         StretchyOperatorShaper::Metrics metrics;
106         vertical_shaper.Shape(&math, target_size, &metrics);
107         EXPECT_NEAR(metrics.advance, 1000, kSizeError);
108         EXPECT_NEAR(metrics.ascent, (i + 1) * 1000, kSizeError);
109         EXPECT_FLOAT_EQ(metrics.descent, 0);
110       }
111 
112       // Shaping of horizontal size variants.
113       {
114         scoped_refptr<ShapeResult> result =
115             horizontal_shaper.Shape(&math, target_size);
116         EXPECT_EQ(TestInfo(result)->NumberOfRunsForTesting(), 1u);
117         EXPECT_EQ(TestInfo(result)->RunInfoForTesting(0).NumGlyphs(), 1u);
118         Glyph expected_variant = i ? h0 + 2 * i : over_brace;
119         EXPECT_EQ(TestInfo(result)->GlyphForTesting(0, 0), expected_variant);
120         EXPECT_NEAR(TestInfo(result)->AdvanceForTesting(0, 0), (i + 1) * 1000,
121                     kSizeError);
122       }
123 
124       // Shaping of vertical size variants.
125       {
126         scoped_refptr<ShapeResult> result =
127             vertical_shaper.Shape(&math, target_size);
128         EXPECT_EQ(TestInfo(result)->NumberOfRunsForTesting(), 1u);
129         EXPECT_EQ(TestInfo(result)->RunInfoForTesting(0).NumGlyphs(), 1u);
130         Glyph expected_variant = i ? v0 + 2 * i : left_brace;
131         EXPECT_EQ(TestInfo(result)->GlyphForTesting(0, 0), expected_variant);
132         EXPECT_NEAR(TestInfo(result)->AdvanceForTesting(0, 0), (i + 1) * 1000,
133                     kSizeError);
134       }
135     }
136   }
137 
138   // Stretch an operator to target sizes (in font units) much larger than 4000.
139   //
140   // This will force an assembly with the following parts:
141   // _____________________________________________________________
142   // Part  | MaxStartOverlap | MaxEndOverlap | Advance | Extender |
143   // h2/v2 |     0           |    1000       | 3000    |   false  |
144   // h1/v1 |    1000         |    1000       | 2000    |   true   |
145   //
146   // For an assembly made of one non-extender glyph h2/v2 and repetition_count
147   // copies of extenders h1/v1, the size is
148   // advance(h2/v2) + repetition_count * (advance(h1/v1) - overlap).
149   //
150   // For repetition_count = k and overlap = 750, the size is X = 1250k + 3000.
151   //
152   // Since the font min overlap is 500, for repetition_count = k - 1 the size
153   // is at most Y = 1500k + 1500.
154   //
155   // Since the max overlap of parts is 1000, for repetition_count = k + 1 the
156   // size is at least Z = 1000k + 4000.
157   //
158   // { X - 4000 = 1250k - 1000 >= 250 >> kSizeError for k >= 1.
159   // { X - Y = 1500 - 250k >= 250 >> kSizeError for k <= 5.
160   // Hence setting the target size to 1250k + 3000 will ensure an assembly of
161   // k + 1 glyphs and overlap close to 750 for 1 <= k <= 5.
162   //
163   // Additionally, X - Z = 250k - 1000 = 250 >> kSizeError for k = 5 so this
164   // case also verifies that the minimal number of repetitions is actually used.
165   //
166   for (unsigned repetition_count = 1; repetition_count <= 5;
167        repetition_count++) {
168     // It is not necessary to decrease the target_size by kSizeError here. The
169     // shaper can just increase overlap by kSizeError / repetition_count to
170     // reduce the actual size of the assembly.
171     float overlap = 750;
172     float target_size = 3000 + repetition_count * (2000 - overlap);
173 
174     // Metrics of horizontal assembly.
175     {
176       StretchyOperatorShaper::Metrics metrics;
177       horizontal_shaper.Shape(&math, target_size, &metrics);
178       EXPECT_NEAR(metrics.advance, target_size, kSizeError);
179       EXPECT_NEAR(metrics.ascent, 1000, kSizeError);
180       EXPECT_FLOAT_EQ(metrics.descent, 0);
181     }
182 
183     // Metrics of vertical assembly.
184     {
185       StretchyOperatorShaper::Metrics metrics;
186       vertical_shaper.Shape(&math, target_size, &metrics);
187       EXPECT_NEAR(metrics.advance, 1000, kSizeError);
188       EXPECT_NEAR(metrics.ascent, target_size, kSizeError);
189       EXPECT_FLOAT_EQ(metrics.descent, 0);
190     }
191 
192     // Shaping of horizontal assembly.
193     // From left to right: h2, h1, h1, h1, ...
194     {
195       scoped_refptr<ShapeResult> result =
196           horizontal_shaper.Shape(&math, target_size);
197 
198       EXPECT_EQ(TestInfo(result)->NumberOfRunsForTesting(), 1u);
199       EXPECT_EQ(TestInfo(result)->RunInfoForTesting(0).NumGlyphs(),
200                 repetition_count + 1);
201       EXPECT_EQ(TestInfo(result)->GlyphForTesting(0, 0), h2);
202       EXPECT_NEAR(TestInfo(result)->AdvanceForTesting(0, 0), 3000 - overlap,
203                   kSizeError);
204       for (unsigned i = 0; i < repetition_count - 1; i++) {
205         EXPECT_EQ(TestInfo(result)->GlyphForTesting(0, i + 1), h1);
206         EXPECT_NEAR(TestInfo(result)->AdvanceForTesting(0, i + 1),
207                     2000 - overlap, kSizeError);
208       }
209       EXPECT_EQ(TestInfo(result)->GlyphForTesting(0, repetition_count), h1);
210       EXPECT_NEAR(TestInfo(result)->AdvanceForTesting(0, repetition_count),
211                   2000, kSizeError);
212     }
213 
214     // Shaping of vertical assembly.
215     // From bottom to top: v2, v1, v1, v1, ...
216     {
217       scoped_refptr<ShapeResult> result =
218           vertical_shaper.Shape(&math, target_size);
219 
220       EXPECT_EQ(TestInfo(result)->NumberOfRunsForTesting(), 1u);
221       EXPECT_EQ(TestInfo(result)->RunInfoForTesting(0).NumGlyphs(),
222                 repetition_count + 1);
223       for (unsigned i = 0; i < repetition_count; i++) {
224         EXPECT_EQ(TestInfo(result)->GlyphForTesting(0, i), v1);
225         EXPECT_NEAR(TestInfo(result)->AdvanceForTesting(0, i), 2000 - overlap,
226                     kSizeError);
227       }
228       EXPECT_EQ(TestInfo(result)->GlyphForTesting(0, repetition_count), v2);
229       EXPECT_NEAR(TestInfo(result)->AdvanceForTesting(0, repetition_count),
230                   3000, kSizeError);
231     }
232   }
233 
234   // Stretch an operator to edge target size values.
235   //
236   // These tests verify that it does not cause any assertion or crashes.
237   {
238     // Zero.
239     float target_size = 0;
240     horizontal_shaper.Shape(&math, target_size);
241     vertical_shaper.Shape(&math, target_size);
242 
243     // Negative.
244     target_size = -5500;
245     horizontal_shaper.Shape(&math, target_size);
246     vertical_shaper.Shape(&math, target_size);
247 
248     // Max limit.
249     target_size = std::numeric_limits<float>::max();
250     horizontal_shaper.Shape(&math, target_size);
251     vertical_shaper.Shape(&math, target_size);
252 
253     // Min limit.
254     target_size = std::numeric_limits<float>::min();
255     horizontal_shaper.Shape(&math, target_size);
256     vertical_shaper.Shape(&math, target_size);
257 
258     // More than the max number of glyphs.
259     // The size of an assembly with one non-extender v2/h2 and k - 1 extenders
260     // h1/v1 and minimal overlap 500 is Y = 1500k + 1500.
261     // So target_size - Y >= 250 >> kSizeError if the assembly does not have
262     // more than the max number of glyphs.
263     target_size = 1500 * HarfBuzzRunGlyphData::kMaxGlyphs + 1750;
264     horizontal_shaper.Shape(&math, target_size);
265     vertical_shaper.Shape(&math, target_size);
266   }
267 }
268 
269 // See third_party/blink/web_tests/external/wpt/mathml/tools/largeop.py
TEST_F(StretchyOperatorShaperTest,MathItalicCorrection)270 TEST_F(StretchyOperatorShaperTest, MathItalicCorrection) {
271   {
272     Font math = CreateMathFont(
273         "largeop-displayoperatorminheight2000-2AFF-italiccorrection3000.woff");
274     StretchyOperatorShaper shaper(
275         kNAryWhiteVerticalBarCodePoint,
276         OpenTypeMathStretchData::StretchAxis::Vertical);
277 
278     // Base size.
279     StretchyOperatorShaper::Metrics metrics;
280     shaper.Shape(&math, 0, &metrics);
281     EXPECT_EQ(metrics.italic_correction, 0);
282 
283     // Larger variant.
284     float target_size = 2000 - kSizeError;
285     shaper.Shape(&math, target_size, &metrics);
286     EXPECT_EQ(metrics.italic_correction, 3000);
287   }
288 
289   {
290     Font math = CreateMathFont(
291         "largeop-displayoperatorminheight7000-2AFF-italiccorrection5000.woff");
292     StretchyOperatorShaper shaper(
293         kNAryWhiteVerticalBarCodePoint,
294         OpenTypeMathStretchData::StretchAxis::Vertical);
295 
296     // Base size.
297     StretchyOperatorShaper::Metrics metrics;
298     shaper.Shape(&math, 0, &metrics);
299     EXPECT_EQ(metrics.italic_correction, 0);
300 
301     // Glyph assembly.
302     float target_size = 7000;
303     shaper.Shape(&math, target_size, &metrics);
304     EXPECT_EQ(metrics.italic_correction, 5000);
305   }
306 }
307 
308 }  // namespace blink
309