blob: 80d94665ab7adc3904a7d9f0665b35548eccf6ba [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/opentype/open_type_math_support.h"
// clang-format off
#include <hb.h>
#include <hb-ot.h>
// clang-format on
#include "base/bind.h"
#include "base/callback.h"
#include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_face.h"
namespace {
// HarfBuzz' hb_position_t is a 16.16 fixed-point value.
float HarfBuzzUnitsToFloat(hb_position_t value) {
static const float kFloatToHbRatio = 1.0f / (1 << 16);
return kFloatToHbRatio * value;
}
// Latin Modern, STIX Two, XITS, Asana, Deja Vu, Libertinus and TeX Gyre fonts
// provide at most 13 size variant and 5 assembly parts.
// See https://chromium-review.googlesource.com/c/chromium/src/+/2074678
unsigned kMaxHarfBuzzRecords = 20;
hb_direction_t HarfBuzzDirection(
blink::OpenTypeMathStretchData::StretchAxis stretch_axis) {
return stretch_axis == blink::OpenTypeMathStretchData::StretchAxis::Horizontal
? HB_DIRECTION_LTR
: HB_DIRECTION_BTT;
}
} // namespace
namespace blink {
bool OpenTypeMathSupport::HasMathData(const HarfBuzzFace* harfbuzz_face) {
if (!harfbuzz_face)
return false;
hb_font_t* font =
harfbuzz_face->GetScaledFont(nullptr, HarfBuzzFace::NoVerticalLayout);
DCHECK(font);
hb_face_t* face = hb_font_get_face(font);
DCHECK(face);
return hb_ot_math_has_data(face);
}
base::Optional<float> OpenTypeMathSupport::MathConstant(
const HarfBuzzFace* harfbuzz_face,
MathConstants constant) {
if (!HasMathData(harfbuzz_face))
return base::nullopt;
hb_font_t* font =
harfbuzz_face->GetScaledFont(nullptr, HarfBuzzFace::NoVerticalLayout);
DCHECK(font);
hb_position_t harfbuzz_value = hb_ot_math_get_constant(
font, static_cast<hb_ot_math_constant_t>(constant));
switch (constant) {
case kScriptPercentScaleDown:
case kScriptScriptPercentScaleDown:
case kRadicalDegreeBottomRaisePercent:
return base::Optional<float>(harfbuzz_value / 100.0);
case kDelimitedSubFormulaMinHeight:
case kDisplayOperatorMinHeight:
case kMathLeading:
case kAxisHeight:
case kAccentBaseHeight:
case kFlattenedAccentBaseHeight:
case kSubscriptShiftDown:
case kSubscriptTopMax:
case kSubscriptBaselineDropMin:
case kSuperscriptShiftUp:
case kSuperscriptShiftUpCramped:
case kSuperscriptBottomMin:
case kSuperscriptBaselineDropMax:
case kSubSuperscriptGapMin:
case kSuperscriptBottomMaxWithSubscript:
case kSpaceAfterScript:
case kUpperLimitGapMin:
case kUpperLimitBaselineRiseMin:
case kLowerLimitGapMin:
case kLowerLimitBaselineDropMin:
case kStackTopShiftUp:
case kStackTopDisplayStyleShiftUp:
case kStackBottomShiftDown:
case kStackBottomDisplayStyleShiftDown:
case kStackGapMin:
case kStackDisplayStyleGapMin:
case kStretchStackTopShiftUp:
case kStretchStackBottomShiftDown:
case kStretchStackGapAboveMin:
case kStretchStackGapBelowMin:
case kFractionNumeratorShiftUp:
case kFractionNumeratorDisplayStyleShiftUp:
case kFractionDenominatorShiftDown:
case kFractionDenominatorDisplayStyleShiftDown:
case kFractionNumeratorGapMin:
case kFractionNumDisplayStyleGapMin:
case kFractionRuleThickness:
case kFractionDenominatorGapMin:
case kFractionDenomDisplayStyleGapMin:
case kSkewedFractionHorizontalGap:
case kSkewedFractionVerticalGap:
case kOverbarVerticalGap:
case kOverbarRuleThickness:
case kOverbarExtraAscender:
case kUnderbarVerticalGap:
case kUnderbarRuleThickness:
case kUnderbarExtraDescender:
case kRadicalVerticalGap:
case kRadicalDisplayStyleVerticalGap:
case kRadicalRuleThickness:
case kRadicalExtraAscender:
case kRadicalKernBeforeDegree:
case kRadicalKernAfterDegree:
return base::Optional<float>(HarfBuzzUnitsToFloat(harfbuzz_value));
default:
NOTREACHED();
}
return base::nullopt;
}
base::Optional<float> OpenTypeMathSupport::MathItalicCorrection(
const HarfBuzzFace* harfbuzz_face,
Glyph glyph) {
if (!harfbuzz_face)
return base::nullopt;
hb_font_t* font =
harfbuzz_face->GetScaledFont(nullptr, HarfBuzzFace::NoVerticalLayout);
return base::Optional<float>(HarfBuzzUnitsToFloat(
hb_ot_math_get_glyph_italics_correction(font, glyph)));
}
template <typename HarfBuzzRecordType>
using GetHarfBuzzMathRecordGetter =
base::OnceCallback<unsigned int(hb_font_t* font,
hb_codepoint_t glyph,
hb_direction_t direction,
unsigned int start_offset,
unsigned int* record_count,
HarfBuzzRecordType* record_array)>;
template <typename HarfBuzzRecordType, typename RecordType>
using HarfBuzzMathRecordConverter =
base::RepeatingCallback<RecordType(HarfBuzzRecordType)>;
template <typename HarfBuzzRecordType, typename RecordType>
Vector<RecordType> GetHarfBuzzMathRecord(
const HarfBuzzFace* harfbuzz_face,
Glyph base_glyph,
OpenTypeMathStretchData::StretchAxis stretch_axis,
GetHarfBuzzMathRecordGetter<HarfBuzzRecordType> getter,
HarfBuzzMathRecordConverter<HarfBuzzRecordType, RecordType> converter,
base::Optional<RecordType> prepended_record) {
hb_font_t* hb_font =
harfbuzz_face->GetScaledFont(nullptr, HarfBuzzFace::NoVerticalLayout);
DCHECK(hb_font);
hb_direction_t hb_stretch_axis = HarfBuzzDirection(stretch_axis);
// In practice, math fonts have, for a given base glyph and stretch axis only
// provide a few GlyphVariantRecords (size variants of increasing sizes) and
// GlyphPartRecords (parts of a glyph assembly) so it is safe to truncate
// the result vector to a small size.
HarfBuzzRecordType chunk[kMaxHarfBuzzRecords];
unsigned int count = kMaxHarfBuzzRecords;
std::move(getter).Run(hb_font, base_glyph, hb_stretch_axis,
0 /* start_offset */, &count, chunk);
// Create the vector to the determined size and initialize it with the results
// converted from HarfBuzz's ones, prepending any optional record.
Vector<RecordType> result;
result.ReserveInitialCapacity(prepended_record ? count + 1 : count);
if (prepended_record)
result.push_back(*prepended_record);
for (unsigned i = 0; i < count; i++) {
result.push_back(converter.Run(chunk[i]));
}
return result;
}
Vector<OpenTypeMathStretchData::GlyphVariantRecord>
OpenTypeMathSupport::GetGlyphVariantRecords(
const HarfBuzzFace* harfbuzz_face,
Glyph base_glyph,
OpenTypeMathStretchData::StretchAxis stretch_axis) {
DCHECK(harfbuzz_face);
DCHECK(base_glyph);
auto getter = base::BindOnce(&hb_ot_math_get_glyph_variants);
auto converter =
base::BindRepeating([](hb_ot_math_glyph_variant_t record)
-> OpenTypeMathStretchData::GlyphVariantRecord {
return record.glyph;
});
return GetHarfBuzzMathRecord(
harfbuzz_face, base_glyph, stretch_axis, std::move(getter),
std::move(converter),
base::Optional<OpenTypeMathStretchData::GlyphVariantRecord>(base_glyph));
}
Vector<OpenTypeMathStretchData::GlyphPartRecord>
OpenTypeMathSupport::GetGlyphPartRecords(
const HarfBuzzFace* harfbuzz_face,
Glyph base_glyph,
OpenTypeMathStretchData::StretchAxis stretch_axis,
float* italic_correction) {
DCHECK(harfbuzz_face);
DCHECK(base_glyph);
auto getter = base::BindOnce(
[](hb_font_t* font, hb_codepoint_t glyph, hb_direction_t direction,
unsigned int start_offset, unsigned int* parts_count,
hb_ot_math_glyph_part_t* parts) {
hb_position_t italic_correction;
return hb_ot_math_get_glyph_assembly(font, glyph, direction,
start_offset, parts_count, parts,
&italic_correction);
});
auto converter =
base::BindRepeating([](hb_ot_math_glyph_part_t record)
-> OpenTypeMathStretchData::GlyphPartRecord {
return {record.glyph,
HarfBuzzUnitsToFloat(record.start_connector_length),
HarfBuzzUnitsToFloat(record.end_connector_length),
HarfBuzzUnitsToFloat(record.full_advance),
record.flags & HB_MATH_GLYPH_PART_FLAG_EXTENDER};
});
Vector<OpenTypeMathStretchData::GlyphPartRecord> parts =
GetHarfBuzzMathRecord(
harfbuzz_face, base_glyph, stretch_axis, std::move(getter),
std::move(converter),
base::Optional<OpenTypeMathStretchData::GlyphPartRecord>());
if (italic_correction && !parts.IsEmpty()) {
hb_font_t* hb_font =
harfbuzz_face->GetScaledFont(nullptr, HarfBuzzFace::NoVerticalLayout);
// A GlyphAssembly subtable exists for the specified font, glyph and stretch
// axis since it has been possible to retrieve the GlyphPartRecords. This
// means that the following call is guaranteed to get an italic correction.
hb_position_t harfbuzz_italic_correction;
hb_ot_math_get_glyph_assembly(hb_font, base_glyph,
HarfBuzzDirection(stretch_axis), 0, nullptr,
nullptr, &harfbuzz_italic_correction);
*italic_correction = HarfBuzzUnitsToFloat(harfbuzz_italic_correction);
}
return parts;
}
} // namespace blink