blob: 84024bbecdad12b8ad9cb3a53f257d4de6784720 [file] [log] [blame]
// Copyright 2015 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.
// clang-format off
#include <hb.h>
#include <hb-aat.h>
// clang-format on
#include "third_party/blink/renderer/platform/fonts/opentype/open_type_caps_support.h"
#include "third_party/harfbuzz-ng/utils/hb_scoped.h"
namespace blink {
namespace {
bool activationSelectorPresent(
hb_face_t* hb_face,
const hb_aat_layout_feature_type_t feature_type,
const hb_aat_layout_feature_selector_t enabled_selector_expectation) {
Vector<hb_aat_layout_feature_selector_info_t> feature_selectors;
unsigned num_feature_selectors = 0;
unsigned default_index = 0;
num_feature_selectors = hb_aat_layout_feature_type_get_selector_infos(
hb_face, feature_type, 0, nullptr, nullptr, nullptr);
feature_selectors.resize(num_feature_selectors);
if (!hb_aat_layout_feature_type_get_selector_infos(
hb_face, feature_type, 0, &num_feature_selectors,
feature_selectors.data(), &default_index)) {
return false;
}
for (hb_aat_layout_feature_selector_info_t selector_info :
feature_selectors) {
if (selector_info.enable == enabled_selector_expectation)
return true;
}
return false;
}
} // namespace
OpenTypeCapsSupport::OpenTypeCapsSupport()
: harfbuzz_face_(nullptr),
requested_caps_(FontDescription::kCapsNormal),
font_support_(FontSupport::kFull),
caps_synthesis_(CapsSynthesis::kNone),
font_format_(FontFormat::kUndetermined) {}
OpenTypeCapsSupport::OpenTypeCapsSupport(
const HarfBuzzFace* harfbuzz_face,
FontDescription::FontVariantCaps requested_caps,
hb_script_t script)
: harfbuzz_face_(harfbuzz_face),
requested_caps_(requested_caps),
font_support_(FontSupport::kFull),
caps_synthesis_(CapsSynthesis::kNone),
font_format_(FontFormat::kUndetermined) {
if (requested_caps != FontDescription::kCapsNormal)
DetermineFontSupport(script);
}
FontDescription::FontVariantCaps OpenTypeCapsSupport::FontFeatureToUse(
SmallCapsIterator::SmallCapsBehavior source_text_case) {
if (font_support_ == FontSupport::kFull)
return requested_caps_;
if (font_support_ == FontSupport::kFallback) {
if (requested_caps_ == FontDescription::FontVariantCaps::kAllPetiteCaps)
return FontDescription::FontVariantCaps::kAllSmallCaps;
if (requested_caps_ == FontDescription::FontVariantCaps::kPetiteCaps ||
(requested_caps_ == FontDescription::FontVariantCaps::kUnicase &&
source_text_case == SmallCapsIterator::kSmallCapsSameCase))
return FontDescription::FontVariantCaps::kSmallCaps;
}
return FontDescription::FontVariantCaps::kCapsNormal;
}
bool OpenTypeCapsSupport::NeedsRunCaseSplitting() {
// Lack of titling case support is ignored, titling case is not synthesized.
return font_support_ != FontSupport::kFull &&
requested_caps_ != FontDescription::kTitlingCaps;
}
bool OpenTypeCapsSupport::NeedsSyntheticFont(
SmallCapsIterator::SmallCapsBehavior run_case) {
if (font_support_ == FontSupport::kFull)
return false;
if (requested_caps_ == FontDescription::kTitlingCaps)
return false;
if (font_support_ == FontSupport::kNone) {
if (run_case == SmallCapsIterator::kSmallCapsUppercaseNeeded &&
(caps_synthesis_ == CapsSynthesis::kLowerToSmallCaps ||
caps_synthesis_ == CapsSynthesis::kBothToSmallCaps))
return true;
if (run_case == SmallCapsIterator::kSmallCapsSameCase &&
(caps_synthesis_ == CapsSynthesis::kUpperToSmallCaps ||
caps_synthesis_ == CapsSynthesis::kBothToSmallCaps)) {
return true;
}
}
return false;
}
CaseMapIntend OpenTypeCapsSupport::NeedsCaseChange(
SmallCapsIterator::SmallCapsBehavior run_case) {
CaseMapIntend case_map_intend = CaseMapIntend::kKeepSameCase;
if (font_support_ == FontSupport::kFull)
return case_map_intend;
switch (run_case) {
case SmallCapsIterator::kSmallCapsSameCase:
case_map_intend =
font_support_ == FontSupport::kFallback &&
(caps_synthesis_ == CapsSynthesis::kBothToSmallCaps ||
caps_synthesis_ == CapsSynthesis::kUpperToSmallCaps)
? CaseMapIntend::kLowerCase
: CaseMapIntend::kKeepSameCase;
break;
case SmallCapsIterator::kSmallCapsUppercaseNeeded:
case_map_intend =
font_support_ != FontSupport::kFallback &&
(caps_synthesis_ == CapsSynthesis::kLowerToSmallCaps ||
caps_synthesis_ == CapsSynthesis::kBothToSmallCaps)
? CaseMapIntend::kUpperCase
: CaseMapIntend::kKeepSameCase;
break;
default:
break;
}
return case_map_intend;
}
OpenTypeCapsSupport::FontFormat OpenTypeCapsSupport::GetFontFormat() const {
if (font_format_ == FontFormat::kUndetermined) {
hb_face_t* hb_face = hb_font_get_face(
harfbuzz_face_->GetScaledFont(nullptr, HarfBuzzFace::NoVerticalLayout));
HbScoped<hb_blob_t> morx_blob(
hb_face_reference_table(hb_face, HB_TAG('m', 'o', 'r', 'x')));
HbScoped<hb_blob_t> mort_blob(
hb_face_reference_table(hb_face, HB_TAG('m', 'o', 'r', 't')));
// TODO(crbug.com/911149): Use hb_aat_layout_has_substitution() for
// has_morx_or_mort and hb_ot_layout_has_substitution() for has_gsub once is
// exposed in HarfBuzz.
bool has_morx_or_mort = hb_blob_get_length(morx_blob.get()) ||
hb_blob_get_length(mort_blob.get());
bool has_gsub = hb_ot_layout_has_substitution(hb_face);
font_format_ = has_morx_or_mort && !has_gsub ? FontFormat::kAat
: FontFormat::kOpenType;
}
return font_format_;
}
bool OpenTypeCapsSupport::SupportsFeature(hb_script_t script,
uint32_t tag) const {
if (GetFontFormat() == FontFormat::kAat)
return SupportsAatFeature(tag);
return SupportsOpenTypeFeature(script, tag);
}
bool OpenTypeCapsSupport::SupportsAatFeature(uint32_t tag) const {
// We only want to detect small-caps and capitals-to-small-capitals features
// for aat-fonts, any other requests are returned as not supported.
if (tag != HB_TAG('s', 'm', 'c', 'p') && tag != HB_TAG('c', '2', 's', 'c')) {
return false;
}
hb_face_t* hb_face = hb_font_get_face(
harfbuzz_face_->GetScaledFont(nullptr, HarfBuzzFace::NoVerticalLayout));
Vector<hb_aat_layout_feature_type_t> aat_features;
unsigned feature_count =
hb_aat_layout_get_feature_types(hb_face, 0, nullptr, nullptr);
aat_features.resize(feature_count);
if (!hb_aat_layout_get_feature_types(hb_face, 0, &feature_count,
aat_features.data()))
return false;
if (tag == HB_TAG('s', 'm', 'c', 'p')) {
// Check for presence of new style (feature id 38) or old style (letter
// case, feature id 3) small caps feature presence, then check for the
// specific required activation selectors.
if (!aat_features.Contains(HB_AAT_LAYOUT_FEATURE_TYPE_LETTER_CASE) &&
!aat_features.Contains(HB_AAT_LAYOUT_FEATURE_TYPE_LOWER_CASE))
return false;
// Check for new style small caps, feature id 38.
if (aat_features.Contains(HB_AAT_LAYOUT_FEATURE_TYPE_LOWER_CASE)) {
if (activationSelectorPresent(
hb_face, HB_AAT_LAYOUT_FEATURE_TYPE_LOWER_CASE,
HB_AAT_LAYOUT_FEATURE_SELECTOR_LOWER_CASE_SMALL_CAPS))
return true;
}
// Check for old style small caps enabling selector, feature id 3.
if (aat_features.Contains(HB_AAT_LAYOUT_FEATURE_TYPE_LETTER_CASE)) {
if (activationSelectorPresent(hb_face,
HB_AAT_LAYOUT_FEATURE_TYPE_LETTER_CASE,
HB_AAT_LAYOUT_FEATURE_SELECTOR_SMALL_CAPS))
return true;
}
// Neither old or new style small caps present.
return false;
}
if (tag == HB_TAG('c', '2', 's', 'c')) {
if (!aat_features.Contains(HB_AAT_LAYOUT_FEATURE_TYPE_UPPER_CASE))
return false;
return activationSelectorPresent(
hb_face, HB_AAT_LAYOUT_FEATURE_TYPE_UPPER_CASE,
HB_AAT_LAYOUT_FEATURE_SELECTOR_UPPER_CASE_SMALL_CAPS);
}
return false;
}
void OpenTypeCapsSupport::DetermineFontSupport(hb_script_t script) {
switch (requested_caps_) {
case FontDescription::kSmallCaps:
if (!SupportsFeature(script, HB_TAG('s', 'm', 'c', 'p'))) {
font_support_ = FontSupport::kNone;
caps_synthesis_ = CapsSynthesis::kLowerToSmallCaps;
}
break;
case FontDescription::kAllSmallCaps:
if (!(SupportsFeature(script, HB_TAG('s', 'm', 'c', 'p')) &&
SupportsFeature(script, HB_TAG('c', '2', 's', 'c')))) {
font_support_ = FontSupport::kNone;
caps_synthesis_ = CapsSynthesis::kBothToSmallCaps;
}
break;
case FontDescription::kPetiteCaps:
if (!SupportsFeature(script, HB_TAG('p', 'c', 'a', 'p'))) {
if (SupportsFeature(script, HB_TAG('s', 'm', 'c', 'p'))) {
font_support_ = FontSupport::kFallback;
} else {
font_support_ = FontSupport::kNone;
caps_synthesis_ = CapsSynthesis::kLowerToSmallCaps;
}
}
break;
case FontDescription::kAllPetiteCaps:
if (!(SupportsFeature(script, HB_TAG('p', 'c', 'a', 'p')) &&
SupportsFeature(script, HB_TAG('c', '2', 'p', 'c')))) {
if (SupportsFeature(script, HB_TAG('s', 'm', 'c', 'p')) &&
SupportsFeature(script, HB_TAG('c', '2', 's', 'c'))) {
font_support_ = FontSupport::kFallback;
} else {
font_support_ = FontSupport::kNone;
caps_synthesis_ = CapsSynthesis::kBothToSmallCaps;
}
}
break;
case FontDescription::kUnicase:
if (!SupportsFeature(script, HB_TAG('u', 'n', 'i', 'c'))) {
caps_synthesis_ = CapsSynthesis::kUpperToSmallCaps;
if (SupportsFeature(script, HB_TAG('s', 'm', 'c', 'p'))) {
font_support_ = FontSupport::kFallback;
} else {
font_support_ = FontSupport::kNone;
}
}
break;
case FontDescription::kTitlingCaps:
if (!SupportsFeature(script, HB_TAG('t', 'i', 't', 'l'))) {
font_support_ = FontSupport::kNone;
}
break;
default:
NOTREACHED();
}
}
} // namespace blink