Commit be0abf51 authored by Frédéric Wang's avatar Frédéric Wang Committed by Commit Bot

[mathml] Implement shaper for stretchy math operator.

This CL implements new API in order to determine metrics of stretchy
math operators and shape them, following the algorithm of [1]. This
will be used to implement stretchy MathML symbols (for radicals and
operators). It relies on information from the OpenType MathVariants
table [2] and corresponding iterators implemented in [3].

[1] https://mathml-refresh.github.io/mathml-core/#the-glyphassembly-table
[2] https://docs.microsoft.com/en-us/typography/opentype/spec/math#mathvariants-table
[3] https://chromium-review.googlesource.com/c/chromium/src/+/2074678

Bug: 6606
Change-Id: I362edd91fa0ff7f1d2da4381dac1e731be1e0858
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2051923Reviewed-by: default avatarKoji Ishii <kojii@chromium.org>
Reviewed-by: default avatarDominik Röttsches <drott@chromium.org>
Commit-Queue: Frédéric Wang <fwang@igalia.com>
Cr-Commit-Position: refs/heads/master@{#746698}
parent baf159d2
...@@ -678,6 +678,8 @@ jumbo_component("platform") { ...@@ -678,6 +678,8 @@ jumbo_component("platform") {
"fonts/shaping/shape_result_view.h", "fonts/shaping/shape_result_view.h",
"fonts/shaping/shaping_line_breaker.cc", "fonts/shaping/shaping_line_breaker.cc",
"fonts/shaping/shaping_line_breaker.h", "fonts/shaping/shaping_line_breaker.h",
"fonts/shaping/stretchy_operator_shaper.cc",
"fonts/shaping/stretchy_operator_shaper.h",
"fonts/simple_font_data.cc", "fonts/simple_font_data.cc",
"fonts/simple_font_data.h", "fonts/simple_font_data.h",
"fonts/skia/font_cache_skia.cc", "fonts/skia/font_cache_skia.cc",
...@@ -1777,6 +1779,7 @@ jumbo_source_set("blink_platform_unittests_sources") { ...@@ -1777,6 +1779,7 @@ jumbo_source_set("blink_platform_unittests_sources") {
"fonts/shaping/shape_result_test.cc", "fonts/shaping/shape_result_test.cc",
"fonts/shaping/shape_result_view_test.cc", "fonts/shaping/shape_result_view_test.cc",
"fonts/shaping/shaping_line_breaker_test.cc", "fonts/shaping/shaping_line_breaker_test.cc",
"fonts/shaping/stretchy_operator_shaper_test.cc",
"fonts/small_caps_iterator_test.cc", "fonts/small_caps_iterator_test.cc",
"fonts/symbols_iterator_test.cc", "fonts/symbols_iterator_test.cc",
"fonts/typesetting_features_test.cc", "fonts/typesetting_features_test.cc",
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include "base/optional.h" #include "base/optional.h"
#include "third_party/blink/renderer/platform/fonts/glyph.h" #include "third_party/blink/renderer/platform/fonts/glyph.h"
#include "third_party/blink/renderer/platform/platform_export.h" #include "third_party/blink/renderer/platform/platform_export.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
namespace blink { namespace blink {
...@@ -28,6 +29,15 @@ class PLATFORM_EXPORT OpenTypeMathStretchData { ...@@ -28,6 +29,15 @@ class PLATFORM_EXPORT OpenTypeMathStretchData {
float full_advance; float full_advance;
bool is_extender; bool is_extender;
}; };
// https://mathml-refresh.github.io/mathml-core/#the-glyphassembly-table
struct AssemblyParameters {
float connector_overlap{0};
unsigned repetition_count{0};
unsigned glyph_count{0};
float stretch_size{0};
Vector<GlyphPartRecord> parts;
};
}; };
} // namespace blink } // namespace blink
......
...@@ -297,7 +297,8 @@ TEST_F(OpenTypeMathSupportTest, MathVariantsWithTable) { ...@@ -297,7 +297,8 @@ TEST_F(OpenTypeMathSupportTest, MathVariantsWithTable) {
auto over_brace = math.PrimaryFont()->GlyphForCharacter(kOverBraceCodePoint); auto over_brace = math.PrimaryFont()->GlyphForCharacter(kOverBraceCodePoint);
// Calculate glyph indices from the last unicode character in the font. // Calculate glyph indices from the last unicode character in the font.
// TODO(fwang): Find a better way to access these glyph indices. // TODO(https://crbug.com/1057596): Find a better way to access these glyph
// indices.
auto v0 = math.PrimaryFont()->GlyphForCharacter( auto v0 = math.PrimaryFont()->GlyphForCharacter(
kArabicMathOperatorHahWithDalCodePoint) + kArabicMathOperatorHahWithDalCodePoint) +
1; 1;
......
...@@ -1450,6 +1450,96 @@ scoped_refptr<ShapeResult> ShapeResult::CreateForSpaces(const Font* font, ...@@ -1450,6 +1450,96 @@ scoped_refptr<ShapeResult> ShapeResult::CreateForSpaces(const Font* font,
return result; return result;
} }
scoped_refptr<ShapeResult> ShapeResult::CreateForStretchyMathOperator(
const Font* font,
TextDirection direction,
OpenTypeMathStretchData::StretchAxis stretch_axis,
Glyph glyph_variant,
float stretch_size) {
bool is_horizontal_assembly =
stretch_axis == OpenTypeMathStretchData::StretchAxis::Horizontal;
unsigned start_index = 0;
unsigned num_characters = 1;
scoped_refptr<ShapeResult> result =
ShapeResult::Create(font, start_index, num_characters, direction);
hb_direction_t hb_direction =
is_horizontal_assembly ? HB_DIRECTION_LTR : HB_DIRECTION_TTB;
unsigned glyph_index = 0;
scoped_refptr<ShapeResult::RunInfo> run = RunInfo::Create(
font->PrimaryFont(), hb_direction, CanvasRotationInVertical::kRegular,
HB_SCRIPT_COMMON, start_index, 1 /* num_glyph */, num_characters);
run->glyph_data_[glyph_index] = {glyph_variant, 0 /* character index */,
true /* IsSafeToBreakBefore */,
stretch_size};
run->width_ = std::max(0.0f, stretch_size);
result->width_ = run->width_;
result->num_glyphs_ = run->NumGlyphs();
result->runs_.push_back(std::move(run));
return result;
}
scoped_refptr<ShapeResult> ShapeResult::CreateForStretchyMathOperator(
const Font* font,
TextDirection direction,
OpenTypeMathStretchData::StretchAxis stretch_axis,
const OpenTypeMathStretchData::AssemblyParameters& assembly_parameters) {
DCHECK(!assembly_parameters.parts.IsEmpty());
DCHECK_LE(assembly_parameters.glyph_count, HarfBuzzRunGlyphData::kMaxGlyphs);
bool is_horizontal_assembly =
stretch_axis == OpenTypeMathStretchData::StretchAxis::Horizontal;
unsigned start_index = 0;
unsigned num_characters = 1;
scoped_refptr<ShapeResult> result =
ShapeResult::Create(font, start_index, num_characters, direction);
hb_direction_t hb_direction =
is_horizontal_assembly ? HB_DIRECTION_LTR : HB_DIRECTION_TTB;
scoped_refptr<ShapeResult::RunInfo> run = RunInfo::Create(
font->PrimaryFont(), hb_direction, CanvasRotationInVertical::kRegular,
HB_SCRIPT_COMMON, start_index, assembly_parameters.glyph_count,
num_characters);
float overlap = assembly_parameters.connector_overlap;
unsigned part_index = 0;
for (const auto& part : assembly_parameters.parts) {
unsigned repetition_count =
part.is_extender ? assembly_parameters.repetition_count : 1;
if (!repetition_count)
continue;
DCHECK(part_index < assembly_parameters.glyph_count);
for (unsigned repetition_index = 0; repetition_index < repetition_count;
repetition_index++) {
unsigned glyph_index =
is_horizontal_assembly
? part_index
: assembly_parameters.glyph_count - 1 - part_index;
float full_advance = glyph_index == assembly_parameters.glyph_count - 1
? part.full_advance
: part.full_advance - overlap;
run->glyph_data_[glyph_index] = {part.glyph, 0 /* character index */,
!glyph_index /* IsSafeToBreakBefore */,
full_advance};
if (!is_horizontal_assembly) {
GlyphOffset glyph_offset(
0, -assembly_parameters.stretch_size + part.full_advance);
run->glyph_data_.SetOffsetAt(glyph_index, glyph_offset);
result->has_vertical_offsets_ |= (glyph_offset.Height() != 0);
}
part_index++;
}
}
run->width_ = std::max(0.0f, assembly_parameters.stretch_size);
result->width_ = run->width_;
result->num_glyphs_ = run->NumGlyphs();
result->runs_.push_back(std::move(run));
return result;
}
void ShapeResult::ToString(StringBuilder* output) const { void ShapeResult::ToString(StringBuilder* output) const {
output->Append("#chars="); output->Append("#chars=");
output->AppendNumber(num_characters_); output->AppendNumber(num_characters_);
......
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
#include "base/containers/span.h" #include "base/containers/span.h"
#include "third_party/blink/renderer/platform/fonts/canvas_rotation_in_vertical.h" #include "third_party/blink/renderer/platform/fonts/canvas_rotation_in_vertical.h"
#include "third_party/blink/renderer/platform/fonts/glyph.h" #include "third_party/blink/renderer/platform/fonts/glyph.h"
#include "third_party/blink/renderer/platform/fonts/opentype/open_type_math_stretch_data.h"
#include "third_party/blink/renderer/platform/fonts/simple_font_data.h" #include "third_party/blink/renderer/platform/fonts/simple_font_data.h"
#include "third_party/blink/renderer/platform/geometry/float_rect.h" #include "third_party/blink/renderer/platform/geometry/float_rect.h"
#include "third_party/blink/renderer/platform/geometry/layout_unit.h" #include "third_party/blink/renderer/platform/geometry/layout_unit.h"
...@@ -142,6 +143,17 @@ class PLATFORM_EXPORT ShapeResult : public RefCounted<ShapeResult> { ...@@ -142,6 +143,17 @@ class PLATFORM_EXPORT ShapeResult : public RefCounted<ShapeResult> {
unsigned start_index, unsigned start_index,
unsigned length, unsigned length,
float width); float width);
static scoped_refptr<ShapeResult> CreateForStretchyMathOperator(
const Font*,
TextDirection,
OpenTypeMathStretchData::StretchAxis,
Glyph,
float stretch_size);
static scoped_refptr<ShapeResult> CreateForStretchyMathOperator(
const Font*,
TextDirection,
OpenTypeMathStretchData::StretchAxis,
const OpenTypeMathStretchData::AssemblyParameters&);
~ShapeResult(); ~ShapeResult();
// Returns a mutable unique instance. If |this| has more than 1 ref count, // Returns a mutable unique instance. If |this| has more than 1 ref count,
...@@ -495,6 +507,7 @@ class PLATFORM_EXPORT ShapeResult : public RefCounted<ShapeResult> { ...@@ -495,6 +507,7 @@ class PLATFORM_EXPORT ShapeResult : public RefCounted<ShapeResult> {
friend class ShapeResultBloberizer; friend class ShapeResultBloberizer;
friend class ShapeResultView; friend class ShapeResultView;
friend class ShapeResultTest; friend class ShapeResultTest;
friend class StretchyOperatorShaper;
template <bool has_non_zero_glyph_offsets> template <bool has_non_zero_glyph_offsets>
float ForEachGlyphImpl(float initial_advance, float ForEachGlyphImpl(float initial_advance,
......
// 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 <hb-ot.h>
#include <hb.h>
#include <unicode/uchar.h>
#include "third_party/blink/renderer/platform/fonts/canvas_rotation_in_vertical.h"
#include "third_party/blink/renderer/platform/fonts/font.h"
#include "third_party/blink/renderer/platform/fonts/opentype/open_type_math_support.h"
#include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_face.h"
#include "third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h"
#include "third_party/blink/renderer/platform/geometry/float_rect.h"
#include "third_party/blink/renderer/platform/wtf/text/unicode.h"
#include "ui/gfx/skia_util.h"
namespace blink {
namespace {
// HarfBuzz' hb_position_t is a 16.16 fixed-point value.
inline float HarfBuzzUnitsToFloat(hb_position_t value) {
static const float kFloatToHbRatio = 1.0f / (1 << 16);
return kFloatToHbRatio * value;
}
inline float GetGlyphStretchSize(
FloatRect bounds,
OpenTypeMathStretchData::StretchAxis stretch_axis) {
return stretch_axis == OpenTypeMathStretchData::StretchAxis::Horizontal
? bounds.Width()
: bounds.Height();
}
inline StretchyOperatorShaper::Metrics ToMetrics(FloatRect bounds) {
return {bounds.Width(), -bounds.Y(), bounds.MaxY()};
}
base::Optional<OpenTypeMathStretchData::AssemblyParameters>
GetAssemblyParameters(const HarfBuzzFace* harfbuzz_face,
Glyph base_glyph,
OpenTypeMathStretchData::StretchAxis stretch_axis,
float target_size) {
Vector<OpenTypeMathStretchData::GlyphPartRecord> parts =
OpenTypeMathSupport::GetGlyphPartRecords(harfbuzz_face, base_glyph,
stretch_axis);
if (parts.IsEmpty())
return base::nullopt;
hb_font_t* hb_font =
harfbuzz_face->GetScaledFont(nullptr, HarfBuzzFace::NoVerticalLayout);
auto hb_stretch_axis =
stretch_axis == OpenTypeMathStretchData::StretchAxis::Horizontal
? HB_DIRECTION_LTR
: HB_DIRECTION_BTT;
// Go over the assembly parts and determine parameters used below.
// https://mathml-refresh.github.io/mathml-core/#the-glyphassembly-table
float min_connector_overlap = HarfBuzzUnitsToFloat(
hb_ot_math_get_min_connector_overlap(hb_font, hb_stretch_axis));
float max_connector_overlap = std::numeric_limits<float>::max();
float non_extender_advance_sum = 0, extender_advance_sum = 0;
unsigned non_extender_count = 0, extender_count = 0;
for (auto& part : parts) {
// Calculate the count and advance sums of extender and non-extender glyphs.
if (part.is_extender) {
extender_count++;
extender_advance_sum += part.full_advance;
} else {
non_extender_count++;
non_extender_advance_sum += part.full_advance;
}
// Take into account start connector length for all but the first glyph.
if (part.is_extender || &part != &parts.front()) {
max_connector_overlap =
std::min(max_connector_overlap, part.start_connector_length);
}
// Take into account end connector length for all but the last glyph.
if (part.is_extender || &part != &parts.back()) {
max_connector_overlap =
std::min(max_connector_overlap, part.end_connector_length);
}
}
// Check validity conditions indicated in MathML core.
float extender_non_overlapping_advance_sum =
extender_advance_sum - min_connector_overlap * extender_count;
if (extender_count == 0 || max_connector_overlap < min_connector_overlap ||
extender_non_overlapping_advance_sum <= 0)
return base::nullopt;
// Calculate the minimal number of repetitions needed to obtain an assembly
// size of size at least target size (r_min in MathML Core).
unsigned repetition_count = std::max<float>(
std::ceil((target_size - non_extender_advance_sum +
min_connector_overlap * (non_extender_count - 1)) /
extender_non_overlapping_advance_sum),
0);
// Calculate the number of glyphs, limiting repetition_count to ensure the
// assembly does not have more than HarfBuzzRunGlyphData::kMaxGlyphs.
DCHECK_LE(non_extender_count, HarfBuzzRunGlyphData::kMaxGlyphs);
repetition_count = std::min<unsigned>(
repetition_count,
(HarfBuzzRunGlyphData::kMaxGlyphs - non_extender_count) / extender_count);
unsigned glyph_count = non_extender_count + repetition_count * extender_count;
DCHECK_LE(glyph_count, HarfBuzzRunGlyphData::kMaxGlyphs);
// Calculate the maximum overlap (called o_max in MathML Core) and the number
// of glyph in such an assembly (called N in MathML Core).
float connector_overlap = max_connector_overlap;
if (glyph_count > 1) {
float max_connector_overlap_theorical =
(non_extender_advance_sum + repetition_count * extender_advance_sum -
target_size) /
(glyph_count - 1);
connector_overlap =
std::max(min_connector_overlap,
std::min(connector_overlap, max_connector_overlap_theorical));
}
// Calculate the assembly size (called AssemblySize(o, r) in MathML Core).
float stretch_size = non_extender_advance_sum +
repetition_count * extender_advance_sum -
connector_overlap * (glyph_count - 1);
return base::Optional<OpenTypeMathStretchData::AssemblyParameters>(
{connector_overlap, repetition_count, glyph_count, stretch_size,
std::move(parts)});
}
} // namespace
StretchyOperatorShaper::Metrics StretchyOperatorShaper::GetMetrics(
const Font* font,
float target_size) const {
const SimpleFontData* primary_font = font->PrimaryFont();
const HarfBuzzFace* harfbuzz_face =
primary_font->PlatformData().GetHarfBuzzFace();
Glyph base_glyph = primary_font->GlyphForCharacter(stretchy_character_);
FloatRect bounds;
// Try different glyph variants.
for (auto& variant : OpenTypeMathSupport::GetGlyphVariantRecords(
harfbuzz_face, base_glyph, stretch_axis_)) {
bounds = primary_font->BoundsForGlyph(variant);
if (GetGlyphStretchSize(bounds, stretch_axis_) >= target_size)
return ToMetrics(bounds);
}
// Try a glyph assembly.
auto params = GetAssemblyParameters(harfbuzz_face, base_glyph, stretch_axis_,
target_size);
if (!params)
return ToMetrics(bounds);
bounds = stretch_axis_ == OpenTypeMathStretchData::StretchAxis::Horizontal
? FloatRect(0, 0, params->stretch_size, 0)
: FloatRect(0, -params->stretch_size, 0, params->stretch_size);
for (auto& part : params->parts) {
// Include dimension of the part, orthogonal to the stretch axis.
auto glyph_bounds = primary_font->BoundsForGlyph(part.glyph);
if (stretch_axis_ == OpenTypeMathStretchData::StretchAxis::Horizontal) {
glyph_bounds.SetX(0);
glyph_bounds.SetWidth(0);
} else {
glyph_bounds.SetY(0);
glyph_bounds.SetHeight(0);
}
bounds.UniteEvenIfEmpty(glyph_bounds);
}
return ToMetrics(bounds);
}
scoped_refptr<ShapeResult> StretchyOperatorShaper::Shape(
const Font* font,
float target_size) const {
const SimpleFontData* primary_font = font->PrimaryFont();
const HarfBuzzFace* harfbuzz_face =
primary_font->PlatformData().GetHarfBuzzFace();
Glyph base_glyph = primary_font->GlyphForCharacter(stretchy_character_);
Glyph glyph_variant;
float glyph_variant_stretch_size;
TextDirection direction = TextDirection::kLtr;
// Try different glyph variants.
for (auto& variant : OpenTypeMathSupport::GetGlyphVariantRecords(
harfbuzz_face, base_glyph, stretch_axis_)) {
glyph_variant = variant;
auto bounds = primary_font->BoundsForGlyph(glyph_variant);
glyph_variant_stretch_size = GetGlyphStretchSize(bounds, stretch_axis_);
if (glyph_variant_stretch_size >= target_size) {
return ShapeResult::CreateForStretchyMathOperator(
font, direction, stretch_axis_, glyph_variant,
glyph_variant_stretch_size);
}
}
// Try a glyph assembly.
auto params = GetAssemblyParameters(harfbuzz_face, base_glyph, stretch_axis_,
target_size);
if (!params) {
return ShapeResult::CreateForStretchyMathOperator(
font, direction, stretch_axis_, glyph_variant,
glyph_variant_stretch_size);
}
return ShapeResult::CreateForStretchyMathOperator(
font, direction, stretch_axis_, std::move(*params));
}
} // namespace blink
// 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.
#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_STRETCHY_OPERATOR_SHAPER_H_
#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_STRETCHY_OPERATOR_SHAPER_H_
#include <base/memory/scoped_refptr.h>
#include <unicode/uchar.h>
#include "third_party/blink/renderer/platform/fonts/glyph.h"
#include "third_party/blink/renderer/platform/fonts/opentype/open_type_math_support.h"
#include "third_party/blink/renderer/platform/text/text_direction.h"
#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
namespace blink {
class Font;
class ShapeResult;
class StretchyOperatorShaper;
// TODO(https://crbug.com/1057589): Add a TextDirection parameter, so that it's
// possible to perform glyph-level (rtlm feature) or character-level mirroring
// before stretching.
// https://mathml-refresh.github.io/mathml-core/#algorithms-for-glyph-stretching
class PLATFORM_EXPORT StretchyOperatorShaper final {
DISALLOW_NEW();
public:
StretchyOperatorShaper(UChar stretchy_character,
OpenTypeMathStretchData::StretchAxis stretch_axis)
: stretchy_character_(stretchy_character), stretch_axis_(stretch_axis) {}
// Returns the metrics of the stretched operator for layout purpose.
// May be called multiple times; font and direction may vary between calls.
// https://mathml-refresh.github.io/mathml-core/#dfn-box-metrics-of-a-stretchy-glyph
struct Metrics {
float advance;
float ascent;
float descent;
// TODO(https://crbug.com/1057592): Add italic correction.
};
Metrics GetMetrics(const Font*, float target_size) const;
// Shape the stretched operator. The coordinates of the glyph(s) use the same
// origin as the rectangle returned by GetMetrics.
// May be called multiple times; font and direction may vary between calls.
// https://mathml-refresh.github.io/mathml-core/#dfn-shape-a-stretchy-glyph
scoped_refptr<ShapeResult> Shape(const Font*, float target_size) const;
~StretchyOperatorShaper() = default;
private:
const UChar stretchy_character_;
const OpenTypeMathStretchData::StretchAxis stretch_axis_;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_FONTS_SHAPING_STRETCHY_OPERATOR_SHAPER_H_
// 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 kArabicMathOperatorHahWithDalCodePoint = 0x1EEF1;
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);
font.Update(nullptr);
}
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(
kArabicMathOperatorHahWithDalCodePoint) +
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.
{
auto metrics = horizontal_shaper.GetMetrics(&math, target_size);
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.
{
auto metrics = vertical_shaper.GetMetrics(&math, target_size);
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.
{
auto metrics = horizontal_shaper.GetMetrics(&math, target_size);
EXPECT_NEAR(metrics.advance, target_size, kSizeError);
EXPECT_NEAR(metrics.ascent, 1000, kSizeError);
EXPECT_FLOAT_EQ(metrics.descent, 0);
}
// Metrics of vertical assembly.
{
auto metrics = vertical_shaper.GetMetrics(&math, target_size);
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);
}
}
} // namespace blink
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment