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") {
"fonts/shaping/shape_result_view.h",
"fonts/shaping/shaping_line_breaker.cc",
"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.h",
"fonts/skia/font_cache_skia.cc",
......@@ -1777,6 +1779,7 @@ jumbo_source_set("blink_platform_unittests_sources") {
"fonts/shaping/shape_result_test.cc",
"fonts/shaping/shape_result_view_test.cc",
"fonts/shaping/shaping_line_breaker_test.cc",
"fonts/shaping/stretchy_operator_shaper_test.cc",
"fonts/small_caps_iterator_test.cc",
"fonts/symbols_iterator_test.cc",
"fonts/typesetting_features_test.cc",
......
......@@ -8,6 +8,7 @@
#include "base/optional.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/wtf/vector.h"
namespace blink {
......@@ -28,6 +29,15 @@ class PLATFORM_EXPORT OpenTypeMathStretchData {
float full_advance;
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
......
......@@ -297,7 +297,8 @@ TEST_F(OpenTypeMathSupportTest, MathVariantsWithTable) {
auto over_brace = math.PrimaryFont()->GlyphForCharacter(kOverBraceCodePoint);
// 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(
kArabicMathOperatorHahWithDalCodePoint) +
1;
......
......@@ -1450,6 +1450,96 @@ scoped_refptr<ShapeResult> ShapeResult::CreateForSpaces(const Font* font,
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 {
output->Append("#chars=");
output->AppendNumber(num_characters_);
......
......@@ -35,6 +35,7 @@
#include "base/containers/span.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/opentype/open_type_math_stretch_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/layout_unit.h"
......@@ -142,6 +143,17 @@ class PLATFORM_EXPORT ShapeResult : public RefCounted<ShapeResult> {
unsigned start_index,
unsigned length,
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();
// Returns a mutable unique instance. If |this| has more than 1 ref count,
......@@ -495,6 +507,7 @@ class PLATFORM_EXPORT ShapeResult : public RefCounted<ShapeResult> {
friend class ShapeResultBloberizer;
friend class ShapeResultView;
friend class ShapeResultTest;
friend class StretchyOperatorShaper;
template <bool has_non_zero_glyph_offsets>
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_
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