blob: 3d9827ddb56ef5cf9a7fb638eb83a7cde3e6863e [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/platform/fonts/shaping/stretchy_operator_shaper.h"
#include "base/memory/scoped_refptr.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/platform/fonts/font.h"
#include "third_party/blink/renderer/platform/fonts/opentype/open_type_types.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_test_info.h"
#include "third_party/blink/renderer/platform/testing/font_test_helpers.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
namespace blink {
namespace {
const UChar32 kLeftBraceCodePoint = '{';
const UChar32 kOverBraceCodePoint = 0x23DE;
const UChar32 kRightwardsFrontTiltedShadowedWhiteArrowCodePoint = 0x1F8AB;
const UChar32 kNAryWhiteVerticalBarCodePoint = 0x2AFF;
float kSizeError = .1;
ShapeResultTestInfo* TestInfo(const scoped_refptr<ShapeResult>& result) {
return static_cast<ShapeResultTestInfo*>(result.get());
}
} // namespace
class StretchyOperatorShaperTest : public testing::Test {
protected:
void SetUp() override {
font_description.SetComputedSize(10.0);
font = Font(font_description);
}
void TearDown() override {}
Font CreateMathFont(const String& name, float size = 1000) {
FontDescription::VariantLigatures ligatures;
return blink::test::CreateTestFont(
"MathTestFont",
blink::test::BlinkWebTestsFontsTestDataPath(String("math/") + name),
size, &ligatures);
}
FontDescription font_description;
Font font;
};
// See createStretchy() in
// third_party/blink/web_tests/external/wpt/mathml/tools/operator-dictionary.py
TEST_F(StretchyOperatorShaperTest, GlyphVariants) {
Font math = CreateMathFont("operators.woff");
StretchyOperatorShaper vertical_shaper(
kLeftBraceCodePoint, OpenTypeMathStretchData::StretchAxis::Vertical);
StretchyOperatorShaper horizontal_shaper(
kOverBraceCodePoint, OpenTypeMathStretchData::StretchAxis::Horizontal);
auto left_brace = math.PrimaryFont()->GlyphForCharacter(kLeftBraceCodePoint);
auto over_brace = math.PrimaryFont()->GlyphForCharacter(kOverBraceCodePoint);
// Calculate glyph indices from the last unicode character in the font.
// TODO(https://crbug.com/1057596): Find a better way to access these glyph
// indices.
auto v0 = math.PrimaryFont()->GlyphForCharacter(
kRightwardsFrontTiltedShadowedWhiteArrowCodePoint) +
1;
auto h0 = v0 + 1;
auto v1 = h0 + 1;
auto h1 = v1 + 1;
auto v2 = h1 + 1;
auto h2 = v2 + 1;
// Stretch operators to target sizes (in font units) 125, 250, 375, 500, 625,
// 750, 875, 1000, 1125, ..., 3750, 3875, 4000.
//
// Shaper tries glyphs over_brace/left_brace, h0/v0, h1/v1, h2/v2, h3/v3 of
// respective sizes 1000, 1000, 2000, 3000 and 4000. It returns the smallest
// glyph larger than the target size.
const unsigned size_count = 4;
const unsigned subdivision = 8;
for (unsigned i = 0; i < size_count; i++) {
for (unsigned j = 1; j <= subdivision; j++) {
// Due to floating-point errors, the actual metrics of the size variants
// might actually be slightly smaller than expected. Reduce the
// target_size by kSizeError to ensure that the shaper picks the desired
// size variant.
float target_size = i * 1000 + (j * 1000 / subdivision) - kSizeError;
// Metrics of horizontal size variants.
{
StretchyOperatorShaper::Metrics metrics;
horizontal_shaper.Shape(&math, target_size, &metrics);
EXPECT_NEAR(metrics.advance, (i + 1) * 1000, kSizeError);
EXPECT_NEAR(metrics.ascent, 1000, kSizeError);
EXPECT_FLOAT_EQ(metrics.descent, 0);
}
// Metrics of vertical size variants.
{
StretchyOperatorShaper::Metrics metrics;
vertical_shaper.Shape(&math, target_size, &metrics);
EXPECT_NEAR(metrics.advance, 1000, kSizeError);
EXPECT_NEAR(metrics.ascent, (i + 1) * 1000, kSizeError);
EXPECT_FLOAT_EQ(metrics.descent, 0);
}
// Shaping of horizontal size variants.
{
scoped_refptr<ShapeResult> result =
horizontal_shaper.Shape(&math, target_size);
EXPECT_EQ(TestInfo(result)->NumberOfRunsForTesting(), 1u);
EXPECT_EQ(TestInfo(result)->RunInfoForTesting(0).NumGlyphs(), 1u);
Glyph expected_variant = i ? h0 + 2 * i : over_brace;
EXPECT_EQ(TestInfo(result)->GlyphForTesting(0, 0), expected_variant);
EXPECT_NEAR(TestInfo(result)->AdvanceForTesting(0, 0), (i + 1) * 1000,
kSizeError);
}
// Shaping of vertical size variants.
{
scoped_refptr<ShapeResult> result =
vertical_shaper.Shape(&math, target_size);
EXPECT_EQ(TestInfo(result)->NumberOfRunsForTesting(), 1u);
EXPECT_EQ(TestInfo(result)->RunInfoForTesting(0).NumGlyphs(), 1u);
Glyph expected_variant = i ? v0 + 2 * i : left_brace;
EXPECT_EQ(TestInfo(result)->GlyphForTesting(0, 0), expected_variant);
EXPECT_NEAR(TestInfo(result)->AdvanceForTesting(0, 0), (i + 1) * 1000,
kSizeError);
}
}
}
// Stretch an operator to target sizes (in font units) much larger than 4000.
//
// This will force an assembly with the following parts:
// _____________________________________________________________
// Part | MaxStartOverlap | MaxEndOverlap | Advance | Extender |
// h2/v2 | 0 | 1000 | 3000 | false |
// h1/v1 | 1000 | 1000 | 2000 | true |
//
// For an assembly made of one non-extender glyph h2/v2 and repetition_count
// copies of extenders h1/v1, the size is
// advance(h2/v2) + repetition_count * (advance(h1/v1) - overlap).
//
// For repetition_count = k and overlap = 750, the size is X = 1250k + 3000.
//
// Since the font min overlap is 500, for repetition_count = k - 1 the size
// is at most Y = 1500k + 1500.
//
// Since the max overlap of parts is 1000, for repetition_count = k + 1 the
// size is at least Z = 1000k + 4000.
//
// { X - 4000 = 1250k - 1000 >= 250 >> kSizeError for k >= 1.
// { X - Y = 1500 - 250k >= 250 >> kSizeError for k <= 5.
// Hence setting the target size to 1250k + 3000 will ensure an assembly of
// k + 1 glyphs and overlap close to 750 for 1 <= k <= 5.
//
// Additionally, X - Z = 250k - 1000 = 250 >> kSizeError for k = 5 so this
// case also verifies that the minimal number of repetitions is actually used.
//
for (unsigned repetition_count = 1; repetition_count <= 5;
repetition_count++) {
// It is not necessary to decrease the target_size by kSizeError here. The
// shaper can just increase overlap by kSizeError / repetition_count to
// reduce the actual size of the assembly.
float overlap = 750;
float target_size = 3000 + repetition_count * (2000 - overlap);
// Metrics of horizontal assembly.
{
StretchyOperatorShaper::Metrics metrics;
horizontal_shaper.Shape(&math, target_size, &metrics);
EXPECT_NEAR(metrics.advance, target_size, kSizeError);
EXPECT_NEAR(metrics.ascent, 1000, kSizeError);
EXPECT_FLOAT_EQ(metrics.descent, 0);
}
// Metrics of vertical assembly.
{
StretchyOperatorShaper::Metrics metrics;
vertical_shaper.Shape(&math, target_size, &metrics);
EXPECT_NEAR(metrics.advance, 1000, kSizeError);
EXPECT_NEAR(metrics.ascent, target_size, kSizeError);
EXPECT_FLOAT_EQ(metrics.descent, 0);
}
// Shaping of horizontal assembly.
// From left to right: h2, h1, h1, h1, ...
{
scoped_refptr<ShapeResult> result =
horizontal_shaper.Shape(&math, target_size);
EXPECT_EQ(TestInfo(result)->NumberOfRunsForTesting(), 1u);
EXPECT_EQ(TestInfo(result)->RunInfoForTesting(0).NumGlyphs(),
repetition_count + 1);
EXPECT_EQ(TestInfo(result)->GlyphForTesting(0, 0), h2);
EXPECT_NEAR(TestInfo(result)->AdvanceForTesting(0, 0), 3000 - overlap,
kSizeError);
for (unsigned i = 0; i < repetition_count - 1; i++) {
EXPECT_EQ(TestInfo(result)->GlyphForTesting(0, i + 1), h1);
EXPECT_NEAR(TestInfo(result)->AdvanceForTesting(0, i + 1),
2000 - overlap, kSizeError);
}
EXPECT_EQ(TestInfo(result)->GlyphForTesting(0, repetition_count), h1);
EXPECT_NEAR(TestInfo(result)->AdvanceForTesting(0, repetition_count),
2000, kSizeError);
}
// Shaping of vertical assembly.
// From bottom to top: v2, v1, v1, v1, ...
{
scoped_refptr<ShapeResult> result =
vertical_shaper.Shape(&math, target_size);
EXPECT_EQ(TestInfo(result)->NumberOfRunsForTesting(), 1u);
EXPECT_EQ(TestInfo(result)->RunInfoForTesting(0).NumGlyphs(),
repetition_count + 1);
for (unsigned i = 0; i < repetition_count; i++) {
EXPECT_EQ(TestInfo(result)->GlyphForTesting(0, i), v1);
EXPECT_NEAR(TestInfo(result)->AdvanceForTesting(0, i), 2000 - overlap,
kSizeError);
}
EXPECT_EQ(TestInfo(result)->GlyphForTesting(0, repetition_count), v2);
EXPECT_NEAR(TestInfo(result)->AdvanceForTesting(0, repetition_count),
3000, kSizeError);
}
}
// Stretch an operator to edge target size values.
//
// These tests verify that it does not cause any assertion or crashes.
{
// Zero.
float target_size = 0;
horizontal_shaper.Shape(&math, target_size);
vertical_shaper.Shape(&math, target_size);
// Negative.
target_size = -5500;
horizontal_shaper.Shape(&math, target_size);
vertical_shaper.Shape(&math, target_size);
// Max limit.
target_size = std::numeric_limits<float>::max();
horizontal_shaper.Shape(&math, target_size);
vertical_shaper.Shape(&math, target_size);
// Min limit.
target_size = std::numeric_limits<float>::min();
horizontal_shaper.Shape(&math, target_size);
vertical_shaper.Shape(&math, target_size);
// More than the max number of glyphs.
// The size of an assembly with one non-extender v2/h2 and k - 1 extenders
// h1/v1 and minimal overlap 500 is Y = 1500k + 1500.
// So target_size - Y >= 250 >> kSizeError if the assembly does not have
// more than the max number of glyphs.
target_size = 1500 * HarfBuzzRunGlyphData::kMaxGlyphs + 1750;
horizontal_shaper.Shape(&math, target_size);
vertical_shaper.Shape(&math, target_size);
}
}
// See third_party/blink/web_tests/external/wpt/mathml/tools/largeop.py
TEST_F(StretchyOperatorShaperTest, MathItalicCorrection) {
{
Font math = CreateMathFont(
"largeop-displayoperatorminheight2000-2AFF-italiccorrection3000.woff");
StretchyOperatorShaper shaper(
kNAryWhiteVerticalBarCodePoint,
OpenTypeMathStretchData::StretchAxis::Vertical);
// Base size.
StretchyOperatorShaper::Metrics metrics;
shaper.Shape(&math, 0, &metrics);
EXPECT_EQ(metrics.italic_correction, 0);
// Larger variant.
float target_size = 2000 - kSizeError;
shaper.Shape(&math, target_size, &metrics);
EXPECT_EQ(metrics.italic_correction, 3000);
}
{
Font math = CreateMathFont(
"largeop-displayoperatorminheight7000-2AFF-italiccorrection5000.woff");
StretchyOperatorShaper shaper(
kNAryWhiteVerticalBarCodePoint,
OpenTypeMathStretchData::StretchAxis::Vertical);
// Base size.
StretchyOperatorShaper::Metrics metrics;
shaper.Shape(&math, 0, &metrics);
EXPECT_EQ(metrics.italic_correction, 0);
// Glyph assembly.
float target_size = 7000;
shaper.Shape(&math, target_size, &metrics);
EXPECT_EQ(metrics.italic_correction, 5000);
}
}
} // namespace blink